Last active
April 25, 2025 22:47
-
-
Save Retrockit/36600422bce3b85ac15e9c827aa50f0e to your computer and use it in GitHub Desktop.
system setup shell script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Ubuntu System Setup Script | |
# | |
# This script automates the installation of commonly used tools on Ubuntu 24.10+. | |
# It is designed to be idempotent and maintainable, allowing easy addition and | |
# removal of tools. | |
# Exit on error, undefined variables, and pipe failures | |
set -euo pipefail | |
# Constants | |
AUTO_MODE="false" | |
readonly SCRIPT_NAME="$(basename "$0")" | |
readonly LOG_FILE="/tmp/${SCRIPT_NAME%.sh}_$(date +%Y%m%d_%H%M%S).log" | |
readonly LOG_MARKER=">>>" | |
readonly DOCKER_KEYRING="/etc/apt/keyrings/docker.asc" | |
readonly DOCKER_SOURCE_LIST="/etc/apt/sources.list.d/docker.list" | |
readonly VSCODE_KEYRING="/etc/apt/keyrings/packages.microsoft.gpg" | |
readonly VSCODE_SOURCE_LIST="/etc/apt/sources.list.d/vscode.list" | |
readonly PODMAN_VERSION="v5.4.2" | |
readonly CRUN_VERSION="1.21" | |
readonly FLATHUB_REPO="https://flathub.org/repo/flathub.flatpakrepo" | |
readonly PYENV_INSTALLER="https://pyenv.run" | |
readonly MISE_INSTALLER="https://mise.run" | |
readonly CONTAINERS_REGISTRIES_CONF="/etc/containers/registries.conf" | |
readonly JETBRAINS_INSTALL_DIR="/home/$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}")/.local/share/JetBrains/Toolbox/bin" | |
readonly JETBRAINS_SYMLINK_DIR="/home/$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}")/.local/bin" | |
# Package arrays - customize these according to your needs | |
readonly SYSTEM_PACKAGES=( | |
"apt-transport-https" | |
"ca-certificates" | |
"curl" | |
"gnupg" | |
"lsb-release" | |
"software-properties-common" | |
"libfuse2" # Required for AppImage support and JetBrains Toolbox | |
"wget" # Required for several installations | |
) | |
readonly DEV_PACKAGES=( | |
"build-essential" | |
"git" | |
"python3" | |
"python3-pip" | |
"neovim" | |
) | |
# Pyenv build dependencies | |
readonly PYENV_DEPENDENCIES=( | |
"build-essential" | |
"libssl-dev" | |
"zlib1g-dev" | |
"libbz2-dev" | |
"libreadline-dev" | |
"libsqlite3-dev" | |
"curl" | |
"git" | |
"libncursesw5-dev" | |
"xz-utils" | |
"tk-dev" | |
"libxml2-dev" | |
"libxmlsec1-dev" | |
"libffi-dev" | |
"liblzma-dev" | |
) | |
readonly UTIL_PACKAGES=( | |
"htop" | |
"tmux" | |
"tree" | |
"unzip" | |
"fish" | |
) | |
# Flatpak packages | |
readonly FLATPAK_PACKAGES=( | |
"flatpak" | |
"gnome-software-plugin-flatpak" | |
) | |
# Docker packages to install | |
readonly DOCKER_PACKAGES=( | |
"docker-ce" | |
"docker-ce-cli" | |
"containerd.io" | |
"docker-buildx-plugin" | |
"docker-compose-plugin" | |
) | |
# Podman build dependencies | |
readonly PODMAN_DEPENDENCIES=( | |
"make" | |
"git" | |
"gcc" | |
"build-essential" | |
"pkgconf" | |
"libtool" | |
"libsystemd-dev" | |
"libprotobuf-c-dev" | |
"libcap-dev" | |
"libseccomp-dev" | |
"libyajl-dev" | |
"go-md2man" | |
"autoconf" | |
"python3" | |
"automake" | |
"golang" | |
"libgpgme-dev" | |
"man" | |
"conmon" | |
"passt" | |
"uidmap" | |
"netavark" | |
) | |
# Helper functions | |
####################################### | |
# Log a message to both stdout and the log file | |
# Globals: | |
# LOG_FILE | |
# LOG_MARKER | |
# Arguments: | |
# Message to log | |
####################################### | |
log() { | |
local timestamp | |
timestamp="$(date +'%Y-%m-%d %H:%M:%S')" | |
echo "[${timestamp}] ${LOG_MARKER} $*" | tee -a "${LOG_FILE}" | |
} | |
####################################### | |
# Log an error message and exit | |
# Globals: | |
# LOG_FILE | |
# Arguments: | |
# Error message | |
####################################### | |
err() { | |
local timestamp | |
timestamp="$(date +'%Y-%m-%d %H:%M:%S')" | |
echo "[${timestamp}] ERROR: $*" | tee -a "${LOG_FILE}" >&2 | |
exit 1 | |
} | |
####################################### | |
# Check if a command exists | |
# Arguments: | |
# Command to check | |
# Returns: | |
# 0 if command exists, 1 otherwise | |
####################################### | |
command_exists() { | |
command -v "$1" >/dev/null 2>&1 | |
} | |
####################################### | |
# Check if a package is installed | |
# Arguments: | |
# Package name | |
# Returns: | |
# 0 if package is installed, 1 otherwise | |
####################################### | |
package_installed() { | |
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -q "install ok installed" | |
} | |
####################################### | |
# Install packages if they are not already installed | |
# Arguments: | |
# List of packages to install | |
####################################### | |
install_packages() { | |
local packages=("$@") | |
local packages_to_install=() | |
local pkg | |
# Check which packages need to be installed | |
for pkg in "${packages[@]}"; do | |
if ! package_installed "${pkg}"; then | |
packages_to_install+=("${pkg}") | |
else | |
log "Package ${pkg} is already installed" | |
fi | |
done | |
# Install missing packages if any | |
if (( ${#packages_to_install[@]} > 0 )); then | |
log "Installing packages: ${packages_to_install[*]}" | |
if ! apt-get install -y "${packages_to_install[@]}"; then | |
err "Failed to install packages: ${packages_to_install[*]}" | |
fi | |
log "Successfully installed: ${packages_to_install[*]}" | |
else | |
log "All packages already installed, skipping" | |
fi | |
} | |
####################################### | |
# Update apt repositories and upgrade system | |
# Globals: | |
# None | |
# Arguments: | |
# None | |
####################################### | |
update_system() { | |
log "Updating apt repositories" | |
if ! apt-get update; then | |
err "Failed to update apt repositories" | |
fi | |
log "Upgrading system packages" | |
if ! apt-get upgrade -y; then | |
err "Failed to upgrade system packages" | |
fi | |
} | |
####################################### | |
# Remove conflicting Docker packages | |
# Globals: | |
# None | |
# Arguments: | |
# None | |
####################################### | |
remove_conflicting_packages() { | |
log "Removing conflicting packages" | |
# List of conflicting packages mentioned in the Docker documentation | |
local conflicting_packages=( | |
"docker.io" | |
"docker-doc" | |
"docker-compose" | |
"docker-compose-v2" | |
"podman-docker" | |
"containerd" | |
"runc" | |
) | |
for pkg in "${conflicting_packages[@]}"; do | |
if package_installed "${pkg}"; then | |
log "Removing conflicting package: ${pkg}" | |
apt-get remove -y "${pkg}" || log "Package ${pkg} not installed or could not be removed" | |
fi | |
done | |
} | |
####################################### | |
# Add Docker repository and install Docker | |
# Globals: | |
# DOCKER_PACKAGES | |
# DOCKER_KEYRING | |
# DOCKER_SOURCE_LIST | |
# Arguments: | |
# None | |
####################################### | |
install_docker() { | |
if command_exists docker && docker --version >/dev/null 2>&1; then | |
log "Docker is already installed" | |
return 0 | |
fi | |
log "Setting up Docker repository" | |
# Ensure required packages are installed | |
apt-get update | |
apt-get install -y ca-certificates curl | |
# Create directory for Docker keyring if it doesn't exist | |
install -m 0755 -d /etc/apt/keyrings | |
# Add Docker's official GPG key | |
if [ ! -f "${DOCKER_KEYRING}" ]; then | |
log "Adding Docker's GPG key" | |
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o "${DOCKER_KEYRING}" | |
chmod a+r "${DOCKER_KEYRING}" | |
fi | |
# Add the repository to Apt sources | |
log "Adding Docker repository to apt sources" | |
echo \ | |
"deb [arch=$(dpkg --print-architecture) signed-by=${DOCKER_KEYRING}] https://download.docker.com/linux/ubuntu \ | |
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ | |
tee "${DOCKER_SOURCE_LIST}" > /dev/null | |
# Update apt repository with the new Docker source | |
apt-get update | |
# Install Docker packages | |
log "Installing Docker packages" | |
install_packages "${DOCKER_PACKAGES[@]}" | |
# Start and enable Docker service | |
log "Enabling Docker service to start on boot" | |
systemctl enable --now docker.service | |
systemctl enable containerd.service | |
} | |
####################################### | |
# Configure Docker post-installation | |
# Globals: | |
# None | |
# Arguments: | |
# None | |
####################################### | |
configure_docker_post_install() { | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
# Create docker group if it doesn't exist | |
if ! getent group docker >/dev/null; then | |
log "Creating docker group" | |
groupadd docker | |
fi | |
# Add user to docker group if not already a member | |
if ! getent group docker | grep -q "\b${current_user}\b"; then | |
log "Adding user ${current_user} to docker group" | |
usermod -aG docker "${current_user}" | |
log "User added to docker group. Please log out and back in for changes to take effect." | |
log "Alternatively, run 'newgrp docker' to activate the changes immediately." | |
else | |
log "User ${current_user} is already in the docker group" | |
fi | |
# Configure Docker to start on boot (already done in install_docker, but including here for clarity) | |
if ! systemctl is-enabled docker.service >/dev/null 2>&1; then | |
log "Enabling Docker service to start on boot" | |
systemctl enable docker.service | |
systemctl enable containerd.service | |
fi | |
# Verify Docker installation | |
if docker run --rm hello-world >/dev/null 2>&1; then | |
log "Docker installation verified successfully" | |
else | |
err "Docker installation verification failed. Please check your installation." | |
fi | |
} | |
####################################### | |
# Install Visual Studio Code | |
# Globals: | |
# VSCODE_KEYRING | |
# VSCODE_SOURCE_LIST | |
# Arguments: | |
# None | |
####################################### | |
install_vscode() { | |
if command_exists code; then | |
log "Visual Studio Code is already installed" | |
return 0 | |
fi | |
log "Installing Visual Studio Code" | |
# Set Microsoft repo preference automatically | |
log "Setting VS Code repository preference" | |
echo "code code/add-microsoft-repo boolean true" | debconf-set-selections | |
# Install dependencies | |
apt-get install -y wget gpg apt-transport-https | |
# Create directory for Microsoft keyring if it doesn't exist | |
install -m 0755 -d /etc/apt/keyrings | |
# Add Microsoft GPG key | |
log "Adding Microsoft GPG key" | |
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg | |
install -D -o root -g root -m 644 packages.microsoft.gpg "${VSCODE_KEYRING}" | |
# Add VS Code repository | |
log "Adding VS Code repository" | |
echo "deb [arch=$(dpkg --print-architecture) signed-by=${VSCODE_KEYRING}] https://packages.microsoft.com/repos/code stable main" | tee "${VSCODE_SOURCE_LIST}" > /dev/null | |
# Clean up | |
rm -f packages.microsoft.gpg | |
# Update package cache and install VS Code | |
apt-get update | |
apt-get install -y code | |
log "Visual Studio Code has been installed successfully" | |
} | |
####################################### | |
# Install JetBrains Toolbox | |
# Globals: | |
# JETBRAINS_INSTALL_DIR | |
# JETBRAINS_SYMLINK_DIR | |
# Arguments: | |
# None | |
####################################### | |
install_jetbrains_toolbox() { | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
local tmp_dir="/tmp" | |
# Check if JetBrains Toolbox is already installed | |
if [ -f "${JETBRAINS_SYMLINK_DIR}/jetbrains-toolbox" ]; then | |
log "JetBrains Toolbox is already installed" | |
return 0 | |
fi | |
log "Installing JetBrains Toolbox" | |
# Ensure libfuse2 is installed (required for AppImage) | |
if ! package_installed "libfuse2"; then | |
log "Installing libfuse2 (required for JetBrains Toolbox)" | |
apt-get install -y libfuse2 | |
fi | |
# Fetch the URL of the latest version | |
log "Fetching the URL of the latest JetBrains Toolbox version" | |
local archive_url | |
archive_url=$(curl -s 'https://data.services.jetbrains.com/products/releases?code=TBA&latest=true&type=release' | grep -Po '"linux":.*?[^\\]",' | awk -F ':' '{print $3,":"$4}'| sed 's/[", ]//g') | |
local archive_filename | |
archive_filename=$(basename "$archive_url") | |
# Download the archive | |
log "Downloading $archive_filename" | |
rm -f "$tmp_dir/$archive_filename" 2>/dev/null || true | |
wget -q --show-progress -cO "$tmp_dir/$archive_filename" "$archive_url" | |
# Extract to the install directory | |
log "Extracting to $JETBRAINS_INSTALL_DIR" | |
mkdir -p "$JETBRAINS_INSTALL_DIR" | |
rm -f "$JETBRAINS_INSTALL_DIR/jetbrains-toolbox" 2>/dev/null || true | |
tar -xzf "$tmp_dir/$archive_filename" -C "$JETBRAINS_INSTALL_DIR" --strip-components=1 | |
rm -f "$tmp_dir/$archive_filename" | |
chmod +x "$JETBRAINS_INSTALL_DIR/jetbrains-toolbox" | |
# Create symlink | |
log "Creating symlink to $JETBRAINS_SYMLINK_DIR/jetbrains-toolbox" | |
mkdir -p "$JETBRAINS_SYMLINK_DIR" | |
rm -f "$JETBRAINS_SYMLINK_DIR/jetbrains-toolbox" 2>/dev/null || true | |
ln -s "$JETBRAINS_INSTALL_DIR/jetbrains-toolbox" "$JETBRAINS_SYMLINK_DIR/jetbrains-toolbox" | |
# Fix ownership for the JetBrains directories | |
chown -R "${current_user}:${current_user}" "$(dirname "$JETBRAINS_INSTALL_DIR")" | |
chown -R "${current_user}:${current_user}" "$JETBRAINS_SYMLINK_DIR" | |
log "JetBrains Toolbox has been installed successfully" | |
log "You can run it by executing 'jetbrains-toolbox' (make sure $JETBRAINS_SYMLINK_DIR is in your PATH)" | |
log "For the first run, you may need to launch it manually as the current user (not as root)" | |
} | |
####################################### | |
# Install mise for the current user | |
# Globals: | |
# MISE_INSTALLER | |
# Arguments: | |
# None | |
####################################### | |
install_mise() { | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
local home_dir="/home/${current_user}" | |
local fish_config_dir="${home_dir}/.config/fish" | |
local fish_completions_dir="${fish_config_dir}/completions" | |
log "Installing mise for user ${current_user}" | |
# Check if mise is already installed | |
if su -l "${current_user}" -c "command -v mise" >/dev/null 2>&1; then | |
log "mise is already installed for user ${current_user}" | |
else | |
# Run the mise installer as the current user | |
log "Running the mise installer script" | |
su -l "${current_user}" -c "curl -fsSL ${MISE_INSTALLER} | sh" | |
# Make sure the fish config directories exist | |
if [ ! -d "${fish_completions_dir}" ]; then | |
log "Creating fish completions directory" | |
su -l "${current_user}" -c "mkdir -p '${fish_completions_dir}'" | |
fi | |
# Add mise activation to fish config | |
local fish_config_file="${fish_config_dir}/config.fish" | |
if [ -f "${fish_config_file}" ]; then | |
if ! grep -q "mise activate" "${fish_config_file}"; then | |
log "Adding mise activation to fish config" | |
su -l "${current_user}" -c "echo '~/.local/bin/mise activate fish | source' >> '${fish_config_file}'" | |
else | |
log "mise activation already configured in fish config" | |
fi | |
else | |
log "Creating fish config with mise activation" | |
su -l "${current_user}" -c "mkdir -p '${fish_config_dir}'" | |
su -l "${current_user}" -c "echo '~/.local/bin/mise activate fish | source' > '${fish_config_file}'" | |
fi | |
# Setup global usage | |
log "Setting up mise global usage" | |
su -l "${current_user}" -c "~/.local/bin/mise use -g usage" | |
# Generate mise completions for fish | |
log "Generating mise completions for fish" | |
su -l "${current_user}" -c "~/.local/bin/mise completion fish > '${fish_completions_dir}/mise.fish'" | |
log "mise has been successfully installed and configured for user ${current_user}" | |
fi | |
} | |
####################################### | |
# Install latest Podman from source | |
# Globals: | |
# PODMAN_VERSION | |
# PODMAN_DEPENDENCIES | |
# Arguments: | |
# None | |
####################################### | |
install_podman() { | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
local build_dir="/tmp/podman_build" | |
# Check if podman is already installed with the required version | |
if command_exists podman; then | |
local installed_version | |
installed_version=$(podman -v | awk '{print $3}') | |
if [[ "${installed_version}" == "${PODMAN_VERSION#v}" ]]; then | |
log "Podman ${PODMAN_VERSION} is already installed" | |
return 0 | |
else | |
log "Podman is installed, but version ${installed_version} does not match required ${PODMAN_VERSION#v}" | |
fi | |
fi | |
log "Installing Podman dependencies" | |
install_packages "${PODMAN_DEPENDENCIES[@]}" | |
# Create build directory | |
mkdir -p "${build_dir}" | |
cd "${build_dir}" || err "Failed to enter ${build_dir} directory" | |
# Clone and build Podman | |
log "Cloning Podman repository" | |
if [ ! -d "${build_dir}/podman" ]; then | |
git clone https://github.com/containers/podman.git | |
fi | |
cd podman || err "Failed to enter podman directory" | |
git fetch --all --tags | |
git checkout "${PODMAN_VERSION}" | |
log "Building Podman ${PODMAN_VERSION}" | |
make clean || true # Ignore if this fails | |
make | |
log "Installing Podman" | |
make install | |
# Verify installation | |
if command_exists podman; then | |
local new_version | |
new_version=$(podman -v | awk '{print $3}') | |
log "Podman ${new_version} installed successfully" | |
else | |
err "Podman installation failed" | |
fi | |
} | |
####################################### | |
# Install latest crun from source | |
# Globals: | |
# CRUN_VERSION | |
# Arguments: | |
# None | |
####################################### | |
install_crun() { | |
local build_dir="/tmp/crun_build" | |
# Check if crun is already installed with the required version | |
if command_exists crun; then | |
local installed_version | |
installed_version=$(crun -v | head -n 1 | awk '{print $3}') | |
if [[ "${installed_version}" == "${CRUN_VERSION}" ]]; then | |
log "crun ${CRUN_VERSION} is already installed" | |
return 0 | |
else | |
log "crun is installed, but version ${installed_version} does not match required ${CRUN_VERSION}" | |
fi | |
fi | |
# Create build directory | |
mkdir -p "${build_dir}" | |
cd "${build_dir}" || err "Failed to enter ${build_dir} directory" | |
# Clone and build crun | |
log "Cloning crun repository" | |
if [ ! -d "${build_dir}/crun" ]; then | |
git clone https://github.com/containers/crun.git | |
fi | |
cd crun || err "Failed to enter crun directory" | |
git fetch --all --tags | |
git checkout "${CRUN_VERSION}" | |
log "Building crun ${CRUN_VERSION}" | |
./autogen.sh | |
./configure | |
make | |
log "Installing crun" | |
make install | |
# Verify installation | |
if command_exists crun; then | |
local new_version | |
new_version=$(crun -v | head -n 1 | awk '{print $3}') | |
log "crun ${new_version} installed successfully" | |
else | |
err "crun installation failed" | |
fi | |
} | |
####################################### | |
# Configure Podman policy.json and registries | |
# Globals: | |
# CONTAINERS_REGISTRIES_CONF | |
# Arguments: | |
# None | |
####################################### | |
configure_podman() { | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
local config_dir="/home/${current_user}/.config/containers" | |
local policy_file="${config_dir}/policy.json" | |
# Create containers config directory if it doesn't exist | |
if [ ! -d "${config_dir}" ]; then | |
log "Creating containers config directory" | |
mkdir -p "${config_dir}" | |
chown "${current_user}:${current_user}" "${config_dir}" | |
fi | |
# Create policy.json if it doesn't exist | |
if [ ! -f "${policy_file}" ]; then | |
log "Creating Podman policy.json file" | |
cat > "${policy_file}" << 'EOF' | |
{ | |
"default": [ | |
{ | |
"type": "insecureAcceptAnything" | |
} | |
] | |
} | |
EOF | |
chown "${current_user}:${current_user}" "${policy_file}" | |
log "Podman policy.json created at ${policy_file}" | |
else | |
log "Podman policy.json already exists at ${policy_file}" | |
fi | |
# Configure unqualified search registries | |
log "Configuring Podman unqualified search registries" | |
# Create containers directory if it doesn't exist | |
if [ ! -d "/etc/containers" ]; then | |
mkdir -p /etc/containers | |
fi | |
# Add unqualified search registries configuration if not already present | |
if [ -f "${CONTAINERS_REGISTRIES_CONF}" ]; then | |
if ! grep -q "unqualified-search-registries" "${CONTAINERS_REGISTRIES_CONF}"; then | |
log "Adding unqualified search registries to ${CONTAINERS_REGISTRIES_CONF}" | |
echo "unqualified-search-registries = ['docker.io', 'quay.io']" | tee -a "${CONTAINERS_REGISTRIES_CONF}" > /dev/null | |
else | |
log "Unqualified search registries already configured in ${CONTAINERS_REGISTRIES_CONF}" | |
fi | |
else | |
log "Creating ${CONTAINERS_REGISTRIES_CONF} with unqualified search registries" | |
echo "unqualified-search-registries = ['docker.io', 'quay.io']" | tee "${CONTAINERS_REGISTRIES_CONF}" > /dev/null | |
fi | |
# Verify Podman configuration | |
if command_exists podman; then | |
podman info --debug > "${LOG_FILE}.podman_info" 2>&1 | |
log "Podman configuration verified. Debug output saved to ${LOG_FILE}.podman_info" | |
else | |
log "Skipping Podman verification as it doesn't appear to be installed correctly" | |
fi | |
} | |
####################################### | |
# Install and configure Flatpak | |
# Globals: | |
# FLATPAK_PACKAGES | |
# FLATHUB_REPO | |
# Arguments: | |
# None | |
####################################### | |
install_flatpak() { | |
log "Installing Flatpak packages" | |
install_packages "${FLATPAK_PACKAGES[@]}" | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
# Add Flathub repository for the current user (not root) | |
log "Adding Flathub repository for user ${current_user}" | |
# We need to run the flatpak command as the current user, not as root | |
su -l "${current_user}" -c "flatpak remote-add --user --if-not-exists flathub ${FLATHUB_REPO}" | |
log "Flatpak installation and configuration completed successfully" | |
} | |
####################################### | |
# Install pyenv for the current user | |
# Globals: | |
# PYENV_DEPENDENCIES | |
# PYENV_INSTALLER | |
# Arguments: | |
# None | |
####################################### | |
install_pyenv() { | |
log "Installing pyenv dependencies" | |
install_packages "${PYENV_DEPENDENCIES[@]}" | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
log "Installing pyenv for user ${current_user}" | |
# Check if pyenv is already installed | |
if su -l "${current_user}" -c "command -v pyenv" >/dev/null 2>&1; then | |
log "pyenv is already installed for user ${current_user}" | |
else | |
log "Installing pyenv using the installer script" | |
su -l "${current_user}" -c "curl -fsSL ${PYENV_INSTALLER} | bash" | |
# Set up shell integration for bash | |
if [ -f "/home/${current_user}/.bashrc" ]; then | |
if ! grep -q "pyenv init" "/home/${current_user}/.bashrc"; then | |
log "Setting up pyenv in .bashrc" | |
cat >> "/home/${current_user}/.bashrc" << 'EOF' | |
# pyenv setup | |
export PYENV_ROOT="$HOME/.pyenv" | |
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH" | |
eval "$(pyenv init -)" | |
eval "$(pyenv virtualenv-init -)" | |
EOF | |
fi | |
fi | |
# Set up shell integration for fish | |
local fish_config_dir="/home/${current_user}/.config/fish" | |
local fish_config_file="${fish_config_dir}/config.fish" | |
if [ ! -d "${fish_config_dir}" ]; then | |
mkdir -p "${fish_config_dir}" | |
chown "${current_user}:${current_user}" "${fish_config_dir}" | |
fi | |
if [ -f "${fish_config_file}" ]; then | |
if ! grep -q "pyenv init" "${fish_config_file}"; then | |
log "Setting up pyenv in fish config" | |
cat >> "${fish_config_file}" << 'EOF' | |
# pyenv setup | |
set -gx PYENV_ROOT $HOME/.pyenv | |
fish_add_path $PYENV_ROOT/bin | |
pyenv init - | source | |
status --is-interactive; and pyenv virtualenv-init - | source | |
EOF | |
chown "${current_user}:${current_user}" "${fish_config_file}" | |
fi | |
else | |
log "Creating fish config with pyenv setup" | |
cat > "${fish_config_file}" << 'EOF' | |
# pyenv setup | |
set -gx PYENV_ROOT $HOME/.pyenv | |
fish_add_path $PYENV_ROOT/bin | |
pyenv init - | source | |
status --is-interactive; and pyenv virtualenv-init - | source | |
EOF | |
chown "${current_user}:${current_user}" "${fish_config_file}" | |
fi | |
log "pyenv installed successfully for user ${current_user}" | |
fi | |
} | |
####################################### | |
# Install and configure fish shell as default | |
# Globals: | |
# None | |
# Arguments: | |
# None | |
####################################### | |
configure_fish_shell() { | |
local current_user | |
current_user=$(logname 2>/dev/null || echo "${SUDO_USER:-$USER}") | |
# Check if fish is already installed | |
if ! command_exists fish; then | |
log "Installing fish shell" | |
install_packages "fish" | |
else | |
log "Fish shell is already installed" | |
fi | |
# Get fish shell path | |
local fish_path | |
fish_path=$(which fish) | |
# Set fish as default shell for the current user | |
if ! grep -q "${fish_path}" "/etc/passwd" | grep "${current_user}"; then | |
log "Setting fish as the default shell for user ${current_user}" | |
chsh -s "${fish_path}" "${current_user}" | |
else | |
log "Fish shell is already the default for user ${current_user}" | |
fi | |
# Create fish config directory if it doesn't exist | |
local fish_config_dir="/home/${current_user}/.config/fish" | |
if [ ! -d "${fish_config_dir}" ]; then | |
log "Creating fish config directory" | |
mkdir -p "${fish_config_dir}" | |
chown "${current_user}:${current_user}" "${fish_config_dir}" | |
fi | |
# Create initial fish config if it doesn't exist | |
local fish_config_file="${fish_config_dir}/config.fish" | |
if [ ! -f "${fish_config_file}" ]; then | |
log "Creating initial fish config file" | |
cat > "${fish_config_file}" << 'EOF' | |
# Fish shell configuration | |
# Add user's private bin to PATH if it exists | |
if test -d "$HOME/bin" | |
fish_add_path "$HOME/bin" | |
end | |
if test -d "$HOME/.local/bin" | |
fish_add_path "$HOME/.local/bin" | |
end | |
# Set environment variables | |
set -gx EDITOR nvim | |
# Custom aliases | |
alias ll='ls -la' | |
alias la='ls -A' | |
alias l='ls -CF' | |
# Fish greeting | |
function fish_greeting | |
echo "Welcome to Fish shell!" | |
end | |
# Load local config if exists | |
if test -f "$HOME/.config/fish/local.fish" | |
source "$HOME/.config/fish/local.fish" | |
end | |
EOF | |
chown "${current_user}:${current_user}" "${fish_config_file}" | |
fi | |
log "Fish shell has been configured successfully" | |
} | |
####################################### | |
# Prompt for system restart unless auto mode is enabled | |
# Globals: | |
# AUTO_MODE | |
# Arguments: | |
# None | |
####################################### | |
prompt_for_restart() { | |
if [[ "${AUTO_MODE}" == "true" ]]; then | |
log "Running in automatic mode. System will not be restarted automatically." | |
log "Please restart your system manually when convenient." | |
return 0 | |
fi | |
local response | |
log "All installations and configurations are complete." | |
log "It is recommended to restart your system to ensure all changes take effect." | |
read -p "Would you like to restart now? (y/n): " -r response | |
if [[ "${response,,}" =~ ^(y|yes)$ ]]; then | |
log "System will restart in 10 seconds. Press Ctrl+C to cancel." | |
sleep 10 | |
reboot | |
else | |
log "Restart skipped. Remember to restart your system later for all changes to take effect." | |
fi | |
} | |
####################################### | |
# Check if the script is being run as root | |
# Arguments: | |
# None | |
# Returns: | |
# 0 if script is run as root, exits otherwise | |
####################################### | |
check_root() { | |
if [[ $EUID -ne 0 ]]; then | |
err "This script must be run as root. Please use sudo." | |
fi | |
} | |
####################################### | |
# Main function | |
# Globals: | |
# SYSTEM_PACKAGES | |
# DEV_PACKAGES | |
# UTIL_PACKAGES | |
# Arguments: | |
# None | |
####################################### | |
main() { | |
log "Starting system setup script" | |
check_root | |
# Parse command line options | |
while [[ $# -gt 0 ]]; do | |
case "$1" in | |
--auto|-a) | |
AUTO_MODE="true" | |
log "Automatic mode enabled - no interactive prompts will be shown" | |
shift | |
;; | |
--help|-h) | |
echo "Usage: $0 [options]" | |
echo "Options:" | |
echo " --auto, -a Run in automatic mode (no interactive prompts)" | |
echo " --help, -h Show this help message" | |
exit 0 | |
;; | |
*) | |
echo "Unknown option: $1" | |
echo "Use --help for usage information" | |
exit 1 | |
;; | |
esac | |
done | |
# Now make AUTO_MODE readonly after argument parsing | |
readonly AUTO_MODE | |
# Update and upgrade system | |
update_system | |
# Install system packages | |
log "Installing system packages" | |
install_packages "${SYSTEM_PACKAGES[@]}" | |
# Install development packages | |
log "Installing development packages" | |
install_packages "${DEV_PACKAGES[@]}" | |
# Install utility packages | |
log "Installing utility packages" | |
install_packages "${UTIL_PACKAGES[@]}" | |
# Install VS Code | |
install_vscode | |
# Install JetBrains Toolbox | |
install_jetbrains_toolbox | |
# Install Flatpak | |
install_flatpak | |
# Remove conflicting Docker packages | |
remove_conflicting_packages | |
# Install Docker | |
install_docker | |
# Configure Docker post-installation | |
configure_docker_post_install | |
# Install Podman from source | |
install_podman | |
# Install crun from source | |
install_crun | |
# Configure Podman | |
configure_podman | |
# Install pyenv | |
install_pyenv | |
# Install mise | |
install_mise | |
# Configure fish shell | |
configure_fish_shell | |
log "System setup completed successfully" | |
log "Note: You may need to log out and back in for the following changes to take effect:" | |
log "- Docker group membership" | |
log "- pyenv initialization" | |
log "- mise initialization" | |
log "- Default shell change to fish" | |
log "Alternatively, run 'newgrp docker' for Docker group and 'exec fish' to start using fish shell immediately" | |
log "To launch JetBrains Toolbox, run 'jetbrains-toolbox' as your normal user (not as root)" | |
} | |
# Prompt for restart | |
prompt_for_restart | |
# Execute main function | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment