#!/usr/bin/env bash
set -e
NO_CONFIRM=false
PRIVILEGED=false
PLAYBOOK_PATH=""
TAGS=""
COLOR_TITLE='\033[1;34m' # Bold Blue
COLOR_INFO='\033[0;36m' # Cyan
COLOR_WARN='\033[0;33m' # Yellow
COLOR_ERROR='\033[0;31m' # Red
COLOR_SUCCESS='\033[0;32m' # Green
COLOR_RESET='\033[0m'
print_help() {
cat << EOF
Usage: $(basename "$0") [OPTIONS]
Options:
-y, --no-confirm Automatically answer yes to all prompts.
--privileged Also run privileged tasks (requires sudo).
--playbook-path
Run ansible-playbook from a local directory instead of ansible-pull.
--tags Limit execution to roles/tasks with the given tags (comma-separated).
-h, --help Show this help message and exit.
EOF
}
print_header() {
local title="$1"
local char_count
char_count=$(printf '%s' "$title" | LC_ALL=C.UTF-8 wc -m)
local width=$((char_count + 3))
local top="╭$(printf '─%.0s' $(seq 1 $width))╮"
local bottom="╰$(printf '─%.0s' $(seq 1 $width))╯"
printf "\n${COLOR_TITLE}%s${COLOR_RESET}\n" "$top"
printf "${COLOR_TITLE}│${COLOR_RESET} %s ${COLOR_TITLE}│${COLOR_RESET}\n" "$title"
printf "${COLOR_TITLE}%s${COLOR_RESET}\n" "$bottom"
}
confirm() {
if [ "$NO_CONFIRM" = true ]; then
return 0
fi
printf " ${COLOR_INFO}➤ %s${COLOR_RESET} [y/N] " "$1"
# Reading from stderr allows to execute the script by piping it to sh
# See https://stackoverflow.com/a/54396662
read response <&2
case "$response" in
[yY][eE][sS] | [yY])
return 0
;;
*)
return 1
;;
esac
}
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO=$ID
printf " ${COLOR_SUCCESS}✔ Detected distribution: %s${COLOR_RESET}\n" "$DISTRO"
else
printf " ${COLOR_ERROR}✖ Cannot determine the distribution. Exiting.${COLOR_RESET}\n"
exit 1
fi
}
ensure_system_package() {
PACKAGE=$1
if command -v $PACKAGE > /dev/null 2>&1; then
printf " ${COLOR_SUCCESS}✔ %s is already installed.${COLOR_RESET}\n" "$PACKAGE"
return
fi
printf " ${COLOR_WARN}⚠ %s is not installed.${COLOR_RESET}\n" "$PACKAGE"
if ! confirm "Do you want to install $PACKAGE? This requires sudo."; then
printf " ${COLOR_ERROR}✖ ERROR: %s is required but not installed.${COLOR_RESET}\n" "$PACKAGE"
exit 1
fi
printf " ${COLOR_INFO}⬇ Installing %s...${COLOR_RESET}\n" "$PACKAGE"
if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
sudo apt-get update
sudo apt-get install -y $PACKAGE
elif [ "$DISTRO" = "arch" ]; then
sudo pacman -Syu --noconfirm $PACKAGE
else
printf " ${COLOR_ERROR}✖ Unsupported distribution: %s${COLOR_RESET}\n" "$DISTRO"
exit 1
fi
if ! command -v "$PACKAGE" > /dev/null 2>&1; then
printf " ${COLOR_ERROR}✖ ERROR: Failed to install %s.${COLOR_RESET}\n" "$PACKAGE"
exit 1
fi
printf " ${COLOR_SUCCESS}✔ %s installed successfully.${COLOR_RESET}\n" "$PACKAGE"
}
ensure_mise() {
if command -v mise > /dev/null 2>&1; then
printf " ${COLOR_SUCCESS}✔ mise is already installed.${COLOR_RESET}\n"
return
fi
printf " ${COLOR_WARN}⚠ mise is not installed.${COLOR_RESET}\n"
if ! confirm "Do you want to install mise?"; then
printf " ${COLOR_ERROR}✖ ERROR: mise is required but not installed.${COLOR_RESET}\n"
exit 1
fi
printf " ${COLOR_INFO}⬇ Installing mise...${COLOR_RESET}\n"
curl https://mise.run | sh
if [ ! -f "$HOME/.local/bin/mise" ]; then
printf " ${COLOR_ERROR}✖ ERROR: mise installation failed.${COLOR_RESET}\n"
exit 1
fi
}
activate_mise() {
eval "$($HOME/.local/bin/mise activate bash --shims)"
printf " ${COLOR_SUCCESS}✔ mise activated.${COLOR_RESET}\n"
}
ensure_mise_packages() {
local CONF_D="$HOME/.config/mise/conf.d"
if [ -d "$HOME/.local/share/mise/installs/python" ] &&
command -v ansible > /dev/null 2>&1; then
printf " ${COLOR_SUCCESS}✔ Required tools are already installed via mise.${COLOR_RESET}\n"
return
fi
if ! confirm "Do you want to install required tools (python, pipx, ansible) using mise?"; then
printf " ${COLOR_ERROR}✖ ERROR: Required tools are not installed.${COLOR_RESET}\n"
exit 1
fi
mkdir -p "$CONF_D"
cat > "$CONF_D/ansible.toml" << 'EOF'
[tools]
python = { version = "latest", install_env = { MISE_PYTHON_COMPILE = "false" } }
pipx = "latest"
ansible = "latest"
EOF
printf " ${COLOR_INFO}⬇ Installing mise packages...${COLOR_RESET}\n"
mise install
printf " ${COLOR_SUCCESS}✔ Required tools installed.${COLOR_RESET}\n"
}
install_ansible_galaxy_collection() {
if ansible-galaxy collection list | grep -q 'community.general'; then
printf " ${COLOR_SUCCESS}✔ community.general collection is already installed.${COLOR_RESET}\n"
return
fi
printf " ${COLOR_INFO}⬇ Installing community.general collection...${COLOR_RESET}\n"
ansible-galaxy collection install community.general
}
run_ansible() {
if ! confirm "Do you wish to execute the playbook?"; then
printf " ${COLOR_WARN}⚠ Skipping ansible execution.${COLOR_RESET}\n"
exit 1
fi
printf "\n 💬 The playbook installs many tools at the user level.\n\
Some require sudo for system-wide installation.\n\
Running without sudo expects those packages pre-installed.\n"
if [ "$NO_CONFIRM" = false ] && [ "$PRIVILEGED" = false ]; then
if confirm "Do you want to also run privileged tasks (requires sudo)?"; then
PRIVILEGED=true
fi
fi
ANSIBLE_FLAGS=()
[ -n "$TAGS" ] && ANSIBLE_FLAGS+=(--tags "$TAGS")
[ "$PRIVILEGED" = true ] && ANSIBLE_FLAGS+=(-e '{"run_privileged": true}')
[ "$PRIVILEGED" = true ] && [ -z "${ANSIBLE_BECOME_PASS+x}" ] && ANSIBLE_FLAGS+=(-K)
if [ -n "$PLAYBOOK_PATH" ]; then
printf " ${COLOR_INFO}📂 Running ansible-playbook from local path: %s${COLOR_RESET}\n" "$PLAYBOOK_PATH"
cd "$PLAYBOOK_PATH"
ansible-playbook local.yml "${ANSIBLE_FLAGS[@]}"
else
ansible-pull -U https://github.com/ll-nick/ansible-config.git "${ANSIBLE_FLAGS[@]}"
fi
printf " ${COLOR_SUCCESS}✔ Ansible execution completed.${COLOR_RESET}\n"
}
main() {
while [ "$#" -gt 0 ]; do
case "$1" in
-y | --no-confirm)
NO_CONFIRM=true
shift
;;
--privileged)
PRIVILEGED=true
shift
;;
--playbook-path)
if [ -z "$2" ]; then
printf "${COLOR_ERROR}✖ --playbook-path requires a directory argument.${COLOR_RESET}\n"
exit 1
fi
PLAYBOOK_PATH="$2"
shift 2
;;
--tags)
if [ -z "$2" ]; then
printf "${COLOR_ERROR}✖ --tags requires a comma-separated list of tags.${COLOR_RESET}\n"
exit 1
fi
TAGS="$2"
shift 2
;;
-h | --help)
print_help
exit 0
;;
*)
printf "${COLOR_ERROR}✖ Unknown option: %s${COLOR_RESET}\n" "$1"
print_help
exit 1
;;
esac
done
export PATH="$HOME/.local/bin:$PATH"
print_header "🔍 Detecting Linux Distribution"
detect_distro
print_header "📦 Checking system packages"
ensure_system_package "curl"
ensure_system_package "git"
print_header "🔧 Checking mise installation"
ensure_mise
activate_mise
print_header "🧰 Checking mise packages"
ensure_mise_packages
install_ansible_galaxy_collection
print_header "🚀 Running ansible"
run_ansible
printf "\n${COLOR_SUCCESS}✔ Deployment completed successfully!${COLOR_RESET}\n"
}
main "$@"