set -euo pipefail log_info() { printf "\e[1;34m[INFO]\e[0m %s\n" "$1" >&2; } log_warn() { printf "\e[1;33m[WARN]\e[0m %s\n" "$1" >&2; } log_error() { printf "\e[1;31m[ERROR]\e[0m %s\n" "$1" >&2; } safe_download() { local -r url="$1" local -r filename="$2" local -r temp_dir="${3:-/tmp}" [ -f "$temp_dir/$filename" ] && rm -f "$temp_dir/$filename" if ! aria2c -x8 -s8 -d "$temp_dir" -o "$filename" "$url"; then log_error "Download failed: $url" exit 1 fi if [ ! -f "$temp_dir/$filename" ]; then log_error "Downloaded file not found: $temp_dir/$filename" exit 1 fi } check_and_install_packages() { local -r packages=("$@") local missing_packages=() for pkg in "${packages[@]}"; do if ! dpkg -s "$pkg" >/dev/null 2>&1; then missing_packages+=("$pkg") fi done if [ ${#missing_packages[@]} -gt 0 ]; then log_info "Installing packages: ${missing_packages[*]}" sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "${missing_packages[@]}" fi } check_and_remove_packages() { local -r packages=("$@") for pkg in "${packages[@]}"; do if dpkg -s "$pkg" >/dev/null 2>&1; then log_info "Removing package: $pkg" sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y "$pkg" fi done } # ---------- # Simple sudo handling if ! sudo -n true 2>/dev/null; then echo "[INFO] This script requires sudo privileges." >&2 fi # Keep sudo alive while true; do sudo -n true 2>/dev/null || break sleep 60 kill -0 "73085" 2>/dev/null || exit done 2>/dev/null & SUDO_KEEPALIVE_PID= trap 'kill "" 2>/dev/null || : ' EXIT # ---------- powerprofilesctl set performance >/dev/null 2>&1 || : # ---------- gsettings set org.gnome.desktop.interface clock-format 12h gsettings set org.gnome.desktop.interface clock-show-date false gsettings set org.gnome.desktop.interface color-scheme prefer-dark gsettings set org.gnome.desktop.interface gtk-theme Yaru-dark gsettings set org.gnome.desktop.interface show-battery-percentage true gsettings set org.gnome.desktop.privacy hide-identity true gsettings set org.gnome.desktop.privacy old-files-age 0 gsettings set org.gnome.desktop.privacy recent-files-max-age 1 gsettings set org.gnome.desktop.privacy remember-app-usage false gsettings set org.gnome.desktop.privacy remember-recent-files false gsettings set org.gnome.desktop.privacy remove-old-temp-files true gsettings set org.gnome.desktop.privacy remove-old-trash-files true gsettings set org.gnome.desktop.privacy show-full-name-in-top-bar false gsettings set org.gnome.desktop.session idle-delay 0 gsettings set org.gnome.desktop.sound allow-volume-above-100-percent true gsettings set org.gnome.desktop.wm.preferences button-layout appmenu:minimize,maximize,close gsettings set org.gnome.mutter center-new-windows true gsettings set org.gnome.nautilus.preferences show-create-link true gsettings set org.gnome.nautilus.preferences show-delete-permanently true gsettings set org.gnome.settings-daemon.plugins.power idle-dim false gsettings set org.gnome.settings-daemon.plugins.power lid-close-ac-action nothing gsettings set org.gnome.settings-daemon.plugins.power lid-close-battery-action nothing gsettings set org.gnome.shell.extensions.dash-to-dock always-center-icons true gsettings set org.gnome.shell.extensions.dash-to-dock autohide false gsettings set org.gnome.shell.extensions.dash-to-dock background-opacity 0 gsettings set org.gnome.shell.extensions.dash-to-dock click-action minimize gsettings set org.gnome.shell.extensions.dash-to-dock dash-max-icon-size 32 gsettings set org.gnome.shell.extensions.dash-to-dock dock-position BOTTOM gsettings set org.gnome.shell.extensions.dash-to-dock intellihide false gsettings set org.gnome.shell.extensions.dash-to-dock scroll-action cycle-windows gsettings set org.gnome.shell.extensions.dash-to-dock show-apps-at-top true gsettings set org.gnome.shell.extensions.dash-to-dock show-mounts false gsettings set org.gnome.shell.extensions.dash-to-dock show-trash false gsettings set org.gnome.shell.extensions.ding show-home false gsettings set org.gnome.shell favorite-apps "['org.gnome.Nautilus.desktop','org.gnome.Terminal.desktop','firefox_firefox.desktop','code.desktop']" # ---------- mkdir -p "$HOME/.local/share/applications" for file in \ /usr/share/applications/gnome-language-selector.desktop \ /usr/share/applications/info.desktop \ /usr/share/applications/nm-connection-editor.desktop \ /usr/share/applications/software-properties-drivers.desktop \ /usr/share/applications/software-properties-gtk.desktop do dest="$HOME/.local/share/applications/$(basename "$file")" if [ ! -f "$dest" ] || ! cmp -s "$file" "$dest"; then cp "$file" "$dest" fi if ! grep -q '^Hidden=true' "$dest" 2>/dev/null; then if grep -q '^Hidden=' "$dest" 2>/dev/null; then sed -i 's/^Hidden=.*/Hidden=true/' "$dest" else printf 'Hidden=true\n' >> "$dest" fi fi done # ---------- sudo systemctl stop whoopsie.path whoopsie.service >/dev/null 2>&1 || : sudo systemctl mask whoopsie.path whoopsie.service >/dev/null 2>&1 || : if snap list 2>/dev/null | grep -qw firmware-updater; then sudo snap remove --purge firmware-updater fi if [ -d /etc/systemd/user/default.target.wants ]; then sudo rm -f /etc/systemd/user/default.target.wants/* || : sudo rmdir --ignore-fail-on-non-empty /etc/systemd/user/default.target.wants || : fi installed_packages=() for pkg in \ apport \ baobab \ eog \ evince \ gnome-calculator \ gnome-characters \ gnome-clocks \ gnome-font-viewer \ gnome-logs \ gnome-power-manager \ gnome-startup-applications \ gnome-system-monitor \ gnome-text-editor \ gnome-user-docs \ seahorse \ ubuntu-docs \ ubuntu-report \ whoopsie \ yelp do dpkg -s "$pkg" >/dev/null 2>&1 && installed_packages+=("$pkg") done if [ ${#installed_packages[@]} -gt 0 ]; then log_info "Removing packages: ${installed_packages[*]}" sudo DEBIAN_FRONTEND=noninteractive apt-get purge -y "${installed_packages[@]}" fi sudo systemctl daemon-reload # ---------- sudo apt-get update check_and_install_packages apt-transport-https aria2 build-essential curl libssl-dev tree if apt-get -s upgrade | grep -q '^Inst '; then log_info "Upgrading system packages" sudo DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y fi updates=$(snap refresh --list 2>&1) if [[ "$updates" == "All snaps up to date." ]]; then log_info "No snap updates available" else log_info "Refreshing snap packages" sudo snap refresh fi # ---------- wallpaper_file="$HOME/.local/share/wallpapers/macos-monterey-wwdc-21-stock-dark-mode-5k-6016x6016-5585.jpg" wallpaper_uri="file://$wallpaper_file" if [ -f "$wallpaper_file" ]; then log_info "Wallpaper already exists" else log_info "Downloading wallpaper" mkdir -p "$HOME/.local/share/wallpapers" safe_download "https://tuxforge.ritchie.app/assets/macos-monterey-wwdc-21-stock-dark-mode-5k-6016x6016-5585.jpg" "macos-monterey-wwdc-21-stock-dark-mode-5k-6016x6016-5585.jpg" mv /tmp/macos-monterey-wwdc-21-stock-dark-mode-5k-6016x6016-5585.jpg "$wallpaper_file" fi gsettings set org.gnome.desktop.background picture-uri "$wallpaper_uri" gsettings set org.gnome.desktop.background picture-uri-dark "$wallpaper_uri" # ---------- if fc-list ":family=FiraCode Nerd Font" | grep -q .; then log_info "FiraCode Nerd Font already installed" else log_info "Installing FiraCode Nerd Font" temp_dir="$(mktemp -d)" pushd "$temp_dir" >/dev/null url_latest="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/FiraCode.tar.xz" safe_download "$url_latest" "FiraCode.tar.xz" "$temp_dir" tar -xJf FiraCode.tar.xz mkdir -p "$HOME/.local/share/fonts/NerdFonts" find . -type f -name "*.ttf" -exec cp -f {} "$HOME/.local/share/fonts/NerdFonts/" \; fc-cache -f popd >/dev/null rm -rf "$temp_dir" fi font='FiraCode Nerd Font' profiles_list="$(gsettings get org.gnome.Terminal.ProfilesList list 2>/dev/null || echo "[]")" uuids=() while read -r u; do uuids+=("$u"); done < <(echo "$profiles_list" | grep -oE "[a-f0-9-]{36}" || :) uuid="" for u in "${uuids[@]}"; do name="$(gsettings get "org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:$u/" visible-name 2>/dev/null || :)" [ "${name//\'/}" = "One Dark" ] && uuid="$u" && break done if [ -z "$uuid" ]; then uuid="$(uuidgen)" uuids+=("$uuid") new_list="[$(printf "'%s'," "${uuids[@]}" | sed 's/,$//')]" gsettings set org.gnome.Terminal.ProfilesList list "$new_list" gsettings set "org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:$uuid/" visible-name "One Dark" fi profile_path="org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:$uuid/" gsettings set "$profile_path" background-color '#1E2127' gsettings set "$profile_path" bold-color '#ABB2BF' gsettings set "$profile_path" bold-color-same-as-fg true gsettings set "$profile_path" cursor-background-color '#5C6370' gsettings set "$profile_path" cursor-colors-set true gsettings set "$profile_path" cursor-foreground-color '#1E2127' gsettings set "$profile_path" font "$font 11" gsettings set "$profile_path" foreground-color '#ABB2BF' gsettings set "$profile_path" highlight-background-color '#3A3F4B' gsettings set "$profile_path" highlight-colors-set true gsettings set "$profile_path" highlight-foreground-color '#ABB2BF' gsettings set "$profile_path" palette "['#000000', '#E06C75', '#98C379', '#D19A66', '#61AFEF', '#C678DD', '#56B6C2', '#ABB2BF', '#5C6370', '#E06C75', '#98C379', '#D19A66', '#61AFEF', '#C678DD', '#56B6C2', '#FFFFFF']" gsettings set "$profile_path" scrollback-lines 20000 gsettings set "$profile_path" use-system-font false gsettings set "$profile_path" use-theme-colors false gsettings set org.gnome.Terminal.ProfilesList default "$uuid" # ---------- if sudo lshw -class display 2>/dev/null | grep -q "NVIDIA"; then if ! dpkg -s nvidia-driver-580 >/dev/null 2>&1; then sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nvidia-driver-580 fi fi # ---------- update_needed=false if [ ! -f /etc/apt/keyrings/docker.gpg ]; then update_needed=true log_info "Installing Docker GPG key" sudo install -d -m0755 /etc/apt/keyrings safe_download "https://download.docker.com/linux/ubuntu/gpg" "docker.gpg.asc" sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg.asc sudo chmod a+r /etc/apt/keyrings/docker.gpg rm -f /tmp/docker.gpg.asc fi if [ ! -f /etc/apt/sources.list.d/docker.sources ]; then update_needed=true log_info "Adding Docker repository" sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<'EOF' Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: noble Components: stable Architectures: amd64 Signed-By: /etc/apt/keyrings/docker.gpg EOF fi if [ "$update_needed" = true ]; then log_info "Updating package lists for Docker repository" sudo apt-get update fi if ! dpkg -s docker-ce >/dev/null 2>&1; then log_info "Installing Docker CE" sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce fi sudo groupadd -f docker id -nG "$USER" | grep -qw docker || sudo usermod -aG docker "$USER" # ---------- firefox_base="$HOME/snap/firefox/common/.mozilla/firefox" mkdir -p "$firefox_base" if [ -f "$firefox_base/profiles.ini" ] && grep -q '^Path=' "$firefox_base/profiles.ini"; then firefox_profile="$(awk -F= '/^Path=/ {print $2; exit}' "$firefox_base/profiles.ini")" else firefox_profile="default.$(date +%s)" fi mkdir -p "$firefox_base/$firefox_profile" if [ ! -f "$firefox_base/$firefox_profile/prefs.js" ]; then log_info "Setting up Firefox profile" safe_download "https://tuxforge.ritchie.app/assets/firefox-backup.tar.xz" "firefox-backup.tar.xz" tar -xJf /tmp/firefox-backup.tar.xz -C "$firefox_base/$firefox_profile" --strip-components=1 rm -f /tmp/firefox-backup.tar.xz fi desired_ini="$(cat < "$firefox_base/profiles.ini" fi # ---------- if dpkg -s code >/dev/null 2>&1; then log_info "VS Code already installed" else log_info "Installing VS Code" safe_download "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64" "code.deb" sudo DEBIAN_FRONTEND=noninteractive dpkg -i /tmp/code.deb sudo apt-get install -f -y rm -f /tmp/code.deb fi if ! dpkg -s code >/dev/null 2>&1 || ! code --list-extensions | grep -q "PKief.material-icon-theme"; then log_info "Installing VS Code extensions" code --install-extension PKief.material-icon-theme code --install-extension qwtel.sqlite-viewer else log_info "VS Code extensions already installed" fi mkdir -p "$HOME/.config/Code/User" cat > "$HOME/.config/Code/User/settings.json" </dev/null || { log_error "SQLite tools extraction failed - files not found" exit 1 } rm -f /tmp/sqlite-tools.zip /tmp/sqlite3 /tmp/sqldiff /tmp/sqlite3_analyzer /tmp/sqlite3_rsync chmod +x "$sqlite_dir"/* else log_info "SQLite tools already installed" fi export PATH="$sqlite_dir:$PATH" if ! grep -q "export PATH=\"$sqlite_dir" "$HOME/.bashrc" 2>/dev/null; then echo "export PATH=\"$sqlite_dir:\$PATH\"" >> "$HOME/.bashrc" fi if ! grep -q "export PATH=\"$sqlite_dir" "$HOME/.profile" 2>/dev/null; then echo "export PATH=\"$sqlite_dir:\$PATH\"" >> "$HOME/.profile" fi # ---------- if sudo find /var/cache/apt/archives -maxdepth 1 -type f -name '*.deb' -print -quit 2>/dev/null | grep -q .; then sudo apt-get clean fi if sudo apt-get -s autoclean 2>/dev/null | grep -q '^Del '; then sudo apt-get autoclean fi if sudo apt-get -s autoremove --purge 2>/dev/null | grep -q '^Remv '; then sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove --purge -y fi if [ -f /var/run/reboot-required ]; then echo -e "\e[1;31mReboot required for all changes to take effect.\e[0m" else echo -e "\e[1;32mNo reboot required. System is fully up to date.\e[0m" fi