diff --git a/update/arch.sh b/update/arch.sh new file mode 100755 index 0000000..c9b7885 --- /dev/null +++ b/update/arch.sh @@ -0,0 +1,405 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$SCRIPT_DIR/common.sh" + +arch::print_banner() { + local host="$1" + printf -- ' \e[H\e[2J\n \e[0;34m.\n \e[0;34m/ \\\n \e[0;34m/ \\\\ \e[1;37m # \e[1;34m| *\n \e[0;34m/^. \\\\ \e[1;37m a##e #%%" a#"e 6##%% \e[1;34m| | |-^-. | | \\ /\n \e[0;34m/ .-. \\\\ \e[1;37m.oOo# # # # # \e[1;34m| | | | | | X\n \e[0;34m/ ( ) _\\\\ \e[1;37m%%OoO# # %%#e" # # \e[1;34m| | | | ^._.| / \\ \e[0;37mTM\n \e[1;34m/ _.~ ~._^\\\\\n \e[1;34m/.^ ^.\\\\ \e[0;37mTM\n\n\n \e[1;32mCMtec '%s' install/Update script.\e[0;37m\n' "$host" +} + +arch::ensure_prerequisites() { + if ! pacman -Qs inetutils >/dev/null; then + sudo pacman -Syy --noconfirm inetutils + fi +} + +arch::maybe_create_snapshot() { + if pacman -Qs timeshift >/dev/null; then + common::log_step "Creating backup/snapshot" + sudo timeshift --create --comments "Update script" + fi +} + +arch::maybe_enable_multilib() { + if roles::enabled GAME; then + if ! grep -q "^\\[multilib\\]" /etc/pacman.conf; then + common::log_step "Enabling multilib" + sudo tee -a /etc/pacman.conf >/dev/null <<'EOT' + +[multilib] +Include = /etc/pacman.d/mirrorlist +EOT + fi + fi +} + +arch::update_system_packages() { + common::log_step "Updating pacman packages" + sudo pacman -Suyy --noconfirm + sudo pacman --noconfirm --needed -S base-devel linux-headers git + common::symlink "$PROJECT_ROOT/gitconfig" "$HOME/.gitconfig" +} + +arch::install_graphics_stack() { + if roles::enabled NVIDIA_GPU || roles::enabled NVIDIA_1080_GPU; then + common::log_step "Installing NVIDIA drivers" + local new_kernel="yes" + if pacman -Qs nvidia-open-dkms >/dev/null; then + new_kernel="no" + elif pacman -Qs nvidia-dkms >/dev/null; then + new_kernel="no" + fi + + sudo pacman --noconfirm --needed -S nvidia-dkms + sudo pacman --noconfirm --needed -S nvidia-utils nvidia-settings opencl-nvidia cuda + + if roles::enabled GAME; then + sudo pacman --noconfirm --needed -S lib32-nvidia-utils + fi + + if [[ "$new_kernel" == "yes" ]]; then + printf -- '\033[33m \n\n***** Nvidia driver updated kernel! Please reboot and run update once more! *****\n\n\033[37m' + exit 0 + fi + else + common::log_step "Installing Intel drivers" + sudo pacman --noconfirm --needed -S mesa intel-media-driver + fi +} + +arch::update_aur_packages() { + common::log_step "Updating AUR packages" + if pacman -Qs yay >/dev/null; then + yay --noconfirm --aur + else + if [[ -d "$HOME/yay-bin" ]]; then + rm -rf "$HOME/yay-bin" + fi + ( + cd "$HOME" + git clone https://aur.archlinux.org/yay-bin.git + cd yay-bin + makepkg --noconfirm -si + ) + rm -rf "$HOME/yay-bin" + fi +} + +arch::update_flatpaks() { + common::log_step "Updating Flatpak packages" + if pacman -Qs flatpak >/dev/null; then + flatpak update -y + else + sudo pacman --noconfirm --needed -S flatpak + flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak install -y flathub com.github.tchx84.Flatseal + fi +} + +arch::install_initial_packages() { + common::log_step "Installing initial system packages" + sudo pacman --noconfirm --needed -S timeshift bc git-lfs cmake gawk wget gettext unzip curl inetutils python python-pip python-pipx python-pipenv python-pynvim rustup + yay --noconfirm -S --needed --aur downgrade + rustup update stable + rustup default stable + common::link_shell_profiles arch + common::ensure_local_bin + common::link_update_shortcut + common::link_start_scripts + sudo localectl set-locale LANG=en_US.UTF-8 +} + +arch::configure_hyprland() { + common::log_step "Installing hyprland" + sudo pacman --noconfirm --needed -S wayvnc nm-connection-editor usbutils plymouth dracut dunst hyprpaper hypridle hyprland hyprlock xdg-desktop-portal-hyprland polkit-gnome xorg-xhost gnome-keyring qt6ct qt6ct gnome-themes-extra qt5-wayland qt6-wayland lxappearance qt5-tools adwaita-fonts gnome-disk-utility hyprpaper tk + + yay --noconfirm -S --needed --aur adwaita-qt5-git + yay --noconfirm -S --needed --aur adwaita-qt6-git + yay --noconfirm -S --needed --aur hyprshot + yay --noconfirm -S --needed --aur walker-bin + + mkdir -p "$HOME/.local/bin" + mkdir -p "$HOME/.config/hypr" + + if [[ -f "$PROJECT_ROOT/config/hypr/hyprland_${HOSTNAME}.conf" ]]; then + common::symlink "$PROJECT_ROOT/config/hypr/hyprland_${HOSTNAME}.conf" "$HOME/.config/hypr/hyprland_extra.conf" + else + : >"$HOME/.config/hypr/hyprland_extra.conf" + fi + + if [[ -f "$PROJECT_ROOT/config/hypr/hypridle_${HOSTNAME}.conf" ]]; then + common::symlink "$PROJECT_ROOT/config/hypr/hypridle_${HOSTNAME}.conf" "$HOME/.config/hypr/hypridle.conf" + else + common::symlink "$PROJECT_ROOT/config/hypr/hypridle.conf" "$HOME/.config/hypr/hypridle.conf" + fi + + common::symlink "$PROJECT_ROOT/config/hypr/hyprland.conf" "$HOME/.config/hypr/hyprland.conf" + common::symlink "$PROJECT_ROOT/wrappedhl" "$HOME/.local/bin/wrappedhl" + common::symlink "$PROJECT_ROOT/config/hypr/hyprpaper.conf" "$HOME/.config/hypr/hyprpaper.conf" + common::symlink "$PROJECT_ROOT/config/gtk-3.0" "$HOME/.config" + common::symlink "$PROJECT_ROOT/config/qt5ct" "$HOME/.config" + common::symlink "$PROJECT_ROOT/config/qt6ct" "$HOME/.config" + common::symlink "$PROJECT_ROOT/config/dunst" "$HOME/.config" + common::symlink "$PROJECT_ROOT/config/walker" "$HOME/.config" + + common::log_step "Installing waybar" + sudo pacman --noconfirm --needed -S waybar + mkdir -p "$HOME/.config/waybar" + common::symlink "$PROJECT_ROOT/config/waybar/config" "$HOME/.config/waybar/config" + common::symlink "$PROJECT_ROOT/config/waybar/style.css" "$HOME/.config/waybar/style.css" + + common::log_step "Installing autologin" + yay --noconfirm -S --needed --aur pam_autologin + sudo /bin/cp -rf "$PROJECT_ROOT/login" /etc/pam.d/login + sudo /bin/cp -rf "$PROJECT_ROOT/getty@.service" /usr/lib/systemd/system/getty@.service + sudo touch /etc/security/autologin.conf + + common::log_step "Installing udisks2" + sudo pacman --noconfirm --needed -S udisks2 udiskie + echo 'ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{UDISKS_FILESYSTEM_SHARED}="1"' | sudo tee /lib/udev/rules.d/99-udisks2.rules + echo 'D /media 0755 root root 0 -' | sudo tee /etc/tmpfiles.d/media.conf + if [[ "$HOSTNAME" == "STEAMBOX" ]]; then + echo "$USER ALL=(ALL:ALL) NOPASSWD: /usr/sbin/udisksctl mount -b /dev/sda1" | sudo tee /etc/sudoers.d/udisksctl + fi + + common::log_step "Installing Audio" + sudo pacman --noconfirm --needed -S pipewire pipewire-pulse pipewire-alsa pavucontrol helvum + mkdir -p "$HOME/.config/pipewire" + common::symlink "$PROJECT_ROOT/config/pipewire/pipewire.conf" "$HOME/.config/pipewire/pipewire.conf" + + common::log_step "Installing Caffeine" + yay --noconfirm -S --needed --aur caffeine-ng +} + +arch::configure_terminal_utilities() { + common::log_step "Installing terminal utilities" + if pacman -Qs gnu-free-fonts >/dev/null; then + sudo pacman --noconfirm -R gnu-free-fonts + fi + sudo pacman --noconfirm --needed -S dysk lazygit alacritty btop ranger tmux fd ttf-nerd-fonts-symbols ttf-roboto-mono-nerd gdu ruby bottom go php luarocks composer jdk-openjdk julia nodejs npm + yay --noconfirm -S --needed --aur neovim-git-bin + + sudo npm install -g neovim + + cargo install tree-sitter-cli ripgrep eza + cargo install zoxide --locked + cargo install starship --locked + + mkdir -p "$HOME/.config/nvim" + mkdir -p "$HOME/.config/alacritty" + mkdir -p "$HOME/.config/lazygit" + mkdir -p "$HOME/.local/bin" + + common::link_tmux_conf + common::link_lua_check + common::link_starship_config + common::link_nvim_config + common::link_alacritty_config + common::link_lazygit_config + common::symlink "$PROJECT_ROOT/start_nvim.sh" "$HOME/.local/bin/start_nvim" + + if [[ -d "$HOME/.local/share/nvim/lazy" ]]; then + nvim --headless "+Lazy! sync" +qa + fi + + common::install_fzf + + common::log_step "Installing LSP servers" + sudo pacman --noconfirm --needed -S prettier stylua python-black shfmt lua-language-server bash-language-server ccls vscode-html-languageserver vscode-json-languageserver marksman pyright yaml-language-server vscode-css-languageserver clang + yay --noconfirm -S --needed --aur dockerfile-language-server + rustup component add rust-analyzer clippy rustfmt + + common::log_step "Installing Rust embedded rp2040" + ( + cd "$HOME" + rustup target add thumbv6m-none-eabi + cargo install elf2uf2-rs --locked + cargo install probe-run + cargo install flip-link + mkdir -p "$HOME/.local/bin" + common::symlink "$PROJECT_ROOT/pico-load.sh" "$HOME/.local/bin/pico-load" + ) + + common::log_step "Installing VirtualHere client" + common::install_virtualhere_client start +} + +arch::install_desktop_base() { + common::log_step "Installing desktop base utilities" + sudo pacman --noconfirm --needed -S cameractrls feh + flatpak install -y flathub com.discordapp.Discord + flatpak install -y flathub com.behringer.XAirEdit + flatpak install -y flathub com.github.vikdevelop.timer + flatpak install -y flathub io.github.efogdev.mpris-timer + flatpak install -y flathub org.remmina.Remmina + yay --noconfirm -S --needed --aur brave-bin + + common::log_step "Installing other fonts" + sudo pacman --noconfirm --needed -S noto-fonts poppler-data adobe-source-code-pro-fonts +} + +arch::install_desktop_work() { + common::log_step "Installing desktop work utilities" + sudo pacman --noconfirm --needed -S kicad kicad-library freecad + flatpak install -y flathub org.kde.krita + flatpak install -y flathub com.prusa3d.PrusaSlicer + flatpak install -y flathub com.jgraph.drawio.desktop + flatpak install -y flathub org.gimp.GIMP +} + +arch::install_music_tools() { + common::log_step "Installing music utilities" + sudo pacman -S --noconfirm --needed pipewire pipewire-jack pipewire-alsa pipewire-pulse gamemode + sudo pacman -S --noconfirm --needed qpwgraph realtime-privileges + flatpak install -y flathub com.bitwig.BitwigStudio + + sudo usermod -a -G realtime,audio,gamemode "$USER" + + sudo tee /etc/security/limits.d/99-pipewire.conf <<'EOF' +@audio soft rtprio 95 +@audio soft memlock unlimited +@audio hard rtprio 95 +@audio hard memlock unlimited +EOF + + common::user_systemctl --now enable pipewire{,-pulse}.{socket,service} filter-chain.service + pw-metadata -n settings 0 clock.min-quantum 128 +} + +arch::install_game_tools() { + common::log_step "Installing game utilities" + sudo pacman -S --noconfirm --needed wine + sudo pacman -S --noconfirm --needed wine-mono wine-gecko qt5-tools + sudo pacman -S --noconfirm --needed steam + sudo pacman -S --noconfirm --needed winetricks onnxruntime mangohud lib32-mangohud gamemode + yay --noconfirm -S --needed --aur protonup-qt protontricks + yay --noconfirm -S --needed --aur lug-helper + yay --noconfirm -S --needed --aur openmpi + yay --noconfirm -S --needed --aur jstest-gtk-git + + if [[ ! -d "$HOME/Games/opentrack" ]]; then + mkdir -p "$HOME/Games" + ( + cd "$HOME/Games" + git clone https://github.com/opentrack/opentrack + cd opentrack/ + mkdir build + cd build + cmake .. + ccmake . + make + make install + cat <<'DESKTOP' | sudo tee /usr/share/applications/opentrack.desktop >/dev/null +[Desktop Entry] +Version=3.1.0 +Type=Application +Name=Opentrack +Exec=$HOME/Games/opentrack/build/install/bin/opentrack -platform xcb +Icon=$HOME/Games/opentrack/contrib/cute-octopus-vector-material_15-1831.jpg +Terminal=false +StartupNotify=true +DESKTOP + sudo update-desktop-database /usr/share/applications + ) + fi + + if [[ "$HOSTNAME" == "SIMONBOX" ]]; then + common::log_step "Installing Simon specific game utilities" + flatpak install -y flathub org.vinegarhq.Sober + sudo pacman -S --noconfirm --needed fluidsynth gamemode gvfs libayatana-appindicator innoextract lib32-gamemode lib32-vkd3d python-pefile python-protobuf vulkan-icd-loader vkd3d lib32-vulkan-icd-loader vulkan-tools xorg-xgamma umu-launcher + sudo pacman -S --noconfirm --needed lutris + fi + + if roles::enabled VR; then + common::log_step "Installing VR utilities" + sudo pacman -S --noconfirm --needed cli11 glib2-devel nlohmann-json glew + yay --noconfirm -S --needed --aur monado-vulkan-layers-git + yay --noconfirm -S --needed --aur wlx-overlay-s-git + yay --noconfirm -S --needed --aur xrgears + yay --noconfirm -S --needed --aur envision-xr-git + fi +} + +arch::install_lab_tools() { + common::log_step "Installing lab utilities" + yay --noconfirm -S --needed --aur ps7_libpicoipp ps7_libpicocv + yay --noconfirm -S --needed --aur picoscope7 + yay --noconfirm -S --needed --aur ps7_libps2000a ps7_libps3000a + yay --noconfirm -S --needed --aur nrf-udev + yay --noconfirm -S --needed --aur nrfconnect-appimage + sudo pacman -S --noconfirm --needed tk python-pyserial + ( + cd "$HOME" + wget -O spm6103_viewer.py "https://git.cmtec.se/cm/spm6103_viewer/-/raw/main/spm6103_viewer.py?ref_type=heads&inline=false" + ) +} + +arch::install_bt_tools() { + common::log_step "Installing BT utilities" + sudo pacman -S --noconfirm --needed bluez bluez-utils blueman + common::sudo_systemctl enable bluetooth.service + common::sudo_systemctl start bluetooth.service +} + +arch::final_message() { + printf -- '\033[32m \n\n***** Update complete! Please reboot. *****\n\n\033[37m' +} + +arch::run() { + sudo -v + arch::ensure_prerequisites + + HOSTNAME=$(common::uppercase_hostname) + export HOSTNAME + roles::load "$HOSTNAME" + + arch::print_banner "$HOSTNAME" + roles::print_summary + + sleep 1 + + arch::maybe_create_snapshot + arch::maybe_enable_multilib + arch::update_system_packages + arch::install_graphics_stack + arch::update_aur_packages + arch::update_flatpaks + arch::install_initial_packages + + if roles::enabled HYPERLAND; then + arch::configure_hyprland + fi + + if roles::enabled TERMINAL; then + arch::configure_terminal_utilities + fi + + if roles::enabled DESKTOP_BASE; then + arch::install_desktop_base + fi + + if roles::enabled DESKTOP_WORK; then + arch::install_desktop_work + fi + + if roles::enabled MUSIC; then + arch::install_music_tools + fi + + if roles::enabled GAME; then + arch::install_game_tools + fi + + if roles::enabled LAB; then + arch::install_lab_tools + fi + + if roles::enabled BT; then + arch::install_bt_tools + fi + + arch::final_message +} diff --git a/update/common.sh b/update/common.sh new file mode 100755 index 0000000..921ea2b --- /dev/null +++ b/update/common.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +PROJECT_ROOT=$(dirname "$SCRIPT_DIR") + +source "$SCRIPT_DIR/roles.sh" + +common::log_step() { + local message="$1" + printf -- '\033[33m %s\n\033[37m' "$message" +} + + +common::log_section() { + local message="$1" + printf -- "\033[32m %s\n\033[37m" "$message" +} + +common::is_debug() { + [[ "${UPDATE_DEBUG:-0}" == "1" ]] +} + +common::debug_skip() { + local message="$1" + common::log_step "[debug] ${message} (skipped)" +} + +common::sudo_systemctl() { + if common::is_debug; then + common::debug_skip "sudo systemctl $*" + else + sudo systemctl "$@" + fi +} + +common::user_systemctl() { + if common::is_debug; then + common::debug_skip "systemctl --user $*" + else + systemctl --user "$@" + fi +} + +common::log_success() { + local message="$1" + printf -- '\033[32m %s\n\033[37m' "$message" +} + +common::uppercase_hostname() { + hostname | tr '[:lower:]' '[:upper:]' +} + +common::ensure_local_bin() { + mkdir -p "$HOME/.local/bin" +} + +common::ensure_config_dir() { + mkdir -p "$HOME/.config" +} + +common::symlink() { + local target="$1" + local link_path="$2" + ln -sf "$target" "$link_path" +} + +common::link_update_shortcut() { + common::ensure_local_bin + common::symlink "$PROJECT_ROOT/update_wrapper.sh" "$HOME/.local/bin/update" +} + +common::link_start_scripts() { + common::ensure_local_bin + common::symlink "$PROJECT_ROOT/start_nvim.sh" "$HOME/.local/bin/start_nvim" + if [[ -f "$PROJECT_ROOT/start_toolbox.sh" ]]; then + common::symlink "$PROJECT_ROOT/start_toolbox.sh" "$HOME/.local/bin/start_toolbox" + fi +} + +common::link_shell_profiles() { + local variant="$1" # arch|ubuntu + case "$variant" in + arch) + common::symlink "$PROJECT_ROOT/bashrc_arch" "$HOME/.bashrc" + common::symlink "$PROJECT_ROOT/gitconfig" "$HOME/.gitconfig" + ;; + ubuntu) + common::symlink "$PROJECT_ROOT/bashrc_ubuntu" "$HOME/.bashrc" + if [[ "${WSL:-no}" == "yes" ]]; then + common::symlink "$PROJECT_ROOT/gitconfig.work" "$HOME/.gitconfig" + else + common::symlink "$PROJECT_ROOT/gitconfig" "$HOME/.gitconfig" + fi + ;; + esac +} + +common::link_tmux_conf() { + common::symlink "$PROJECT_ROOT/tmux.conf" "$HOME/.tmux.conf" +} + +common::link_lua_check() { + common::symlink "$PROJECT_ROOT/luacheckrc" "$HOME/.luacheckrc" +} + +common::link_starship_config() { + mkdir -p "$HOME/.config" + common::symlink "$PROJECT_ROOT/config/starship.toml" "$HOME/.config/starship.toml" +} + +common::link_nvim_config() { + mkdir -p "$HOME/.config/nvim" + common::symlink "$PROJECT_ROOT/config/nvim/init.lua" "$HOME/.config/nvim/init.lua" +} + +common::link_alacritty_config() { + mkdir -p "$HOME/.config/alacritty" + common::symlink "$PROJECT_ROOT/config/alacritty/alacritty.toml" "$HOME/.config/alacritty/alacritty.toml" +} + +common::link_lazygit_config() { + mkdir -p "$HOME/.config/lazygit" + common::symlink "$PROJECT_ROOT/config/lazygit/config.yml" "$HOME/.config/lazygit/config.yml" +} + +common::install_fzf() { + common::log_step "Installing fzf" + rm -rf "$HOME/.fzf" + git clone --depth 1 https://github.com/junegunn/fzf.git "$HOME/.fzf" + "$HOME/.fzf/install" --all + if [[ -f "$HOME/.fzf.bash" ]]; then + # shellcheck disable=SC1090 + source "$HOME/.fzf.bash" + fi +} + +common::install_virtualhere_client() { + local manage_service="$1" # start|skip + common::log_step "Installing VirtualHere client" + cd "$HOME" + wget https://www.virtualhere.com/sites/default/files/usbclient/scripts/virtualhereclient.service + wget https://www.virtualhere.com/sites/default/files/usbclient/vhclientx86_64 + wget https://www.virtualhere.com/sites/default/files/usbclient/vhuit64 + chmod +x ./vhclientx86_64 + chmod +x ./vhuit64 + sudo mv ./vhclientx86_64 /usr/sbin + sudo mv ./vhuit64 /usr/sbin + echo "$USER ALL=(ALL:ALL) NOPASSWD: /usr/sbin/vhclientx86_64" | sudo tee "/etc/sudoers.d/${USER}+vhclientx86_64" + echo "$USER ALL=(ALL:ALL) NOPASSWD: /usr/sbin/vhuit64" | sudo tee "/etc/sudoers.d/${USER}+vhuit64" + sudo mv virtualhereclient.service /etc/systemd/system/virtualhereclient.service + if [[ "$manage_service" == "start" ]]; then + common::sudo_systemctl daemon-reload + common::sudo_systemctl enable virtualhereclient.service + common::sudo_systemctl start virtualhereclient.service + fi +} + + +common::print_completion() { + local message="$1" + common::log_success "${message}" +} diff --git a/update/debug.sh b/update/debug.sh new file mode 100755 index 0000000..64b5cae --- /dev/null +++ b/update/debug.sh @@ -0,0 +1,153 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +PROJECT_ROOT=$(dirname "$SCRIPT_DIR") + +usage() { + cat <<'USAGE' +Usage: update/debug.sh [--keep] [arch|ubuntu] [-- update-args...] + +Spins up a temporary VM-like container, mounts the repository, and runs the update +script with UPDATE_DEBUG=1. Requires podman (preferred) or docker with systemd +support. Pass --keep to leave the container running for inspection. Any arguments +after -- are forwarded to update/update.sh. +USAGE +} + +KEEP_CONTAINER=0 +DISTRO="" +FORWARDED_ARGS=() + +while (( $# )); do + case "$1" in + --keep) + KEEP_CONTAINER=1 + shift + ;; + arch|ubuntu) + DISTRO="$1" + shift + ;; + --) + shift + FORWARDED_ARGS=("$@") + break + ;; + -h|--help) + usage + exit 0 + ;; + *) + usage + exit 1 + ;; + esac +done + +if [[ -z "$DISTRO" ]]; then + DISTRO="ubuntu" +fi + +case "$DISTRO" in + arch) + IMAGE="docker.io/archlinux:latest" + INIT_PATH="/usr/lib/systemd/systemd" + PREPARE_CMD='pacman -Syyu --noconfirm sudo git base-devel'; + ;; + ubuntu) + IMAGE="docker.io/library/ubuntu:24.04" + INIT_PATH="/sbin/init" + PREPARE_CMD='apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y sudo git' + ;; + *) + usage + exit 1 + ;; + esac + +if command -v podman >/dev/null 2>&1; then + RUNTIME="podman" + MOUNT_OPTS=":Z" +elif command -v docker >/dev/null 2>&1; then + RUNTIME="docker" + MOUNT_OPTS="" +else + echo "[!] Neither podman nor docker found in PATH." >&2 + exit 1 +fi + +CONTAINER_NAME="linuxbox-update-debug-${DISTRO}-$$" + +cleanup() { + if (( KEEP_CONTAINER )); then + echo "[i] Keeping container ${CONTAINER_NAME} running for inspection." >&2 + return + fi + if "$RUNTIME" ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER_NAME"; then + "$RUNTIME" rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT + +start_container() { + if [[ "$RUNTIME" == "podman" ]]; then + "$RUNTIME" run -d --name "$CONTAINER_NAME" --privileged \ + --tmpfs /tmp --tmpfs /run \ + -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ + -v "$PROJECT_ROOT:/workspace${MOUNT_OPTS}" \ + -w /workspace "$IMAGE" "$INIT_PATH" >/dev/null + else + "$RUNTIME" run -d --name "$CONTAINER_NAME" --privileged --cgroupns=host \ + --tmpfs /tmp --tmpfs /run \ + -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ + -v "$PROJECT_ROOT:/workspace" \ + -w /workspace "$IMAGE" "$INIT_PATH" >/dev/null + fi +} + +wait_for_systemd() { + local attempts=0 + while (( attempts < 30 )); do + local status + status=$("$RUNTIME" exec "$CONTAINER_NAME" systemctl is-system-running 2>/dev/null || true) + case "$status" in + running|degraded) + return 0 + ;; + esac + sleep 1 + ((attempts++)) + done + echo "[!] systemd did not reach running state inside container (last status: $status)." >&2 +} + +bootstrap_container() { + if [[ "$DISTRO" == "ubuntu" ]]; then + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "export DEBIAN_FRONTEND=noninteractive; $PREPARE_CMD" + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "useradd -m debugger || true" + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "echo 'debugger ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/debugger && chmod 0440 /etc/sudoers.d/debugger" + else + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "$PREPARE_CMD" + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "useradd -m debugger || true" + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "echo 'debugger ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/debugger && chmod 0440 /etc/sudoers.d/debugger" + fi + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "chown -R debugger:debugger /workspace" +} + +run_update() { + local update_cmd + printf -v update_cmd 'UPDATE_DEBUG=1 ./update/update.sh %q' "$DISTRO" + for arg in "${FORWARDED_ARGS[@]}"; do + printf -v update_cmd '%s %q' "$update_cmd" "$arg" + done + "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "su - debugger -c \"cd /workspace && $update_cmd\"" +} + +start_container +wait_for_systemd +bootstrap_container +run_update + +echo "[✓] Debug run completed." >&2 diff --git a/update/roles.sh b/update/roles.sh new file mode 100755 index 0000000..ec7765e --- /dev/null +++ b/update/roles.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Known roles used by the update scripts. +UPDATE_KNOWN_ROLES=( + MUSIC + LAB + BT + GAME + VR + DESKTOP_BASE + DESKTOP_WORK + CODE + NVIDIA_GPU + NVIDIA_1080_GPU + TERMINAL + HYPERLAND +) + +# Map hostnames to the roles they should enable. +declare -A ROLES_BY_HOST=( + [CMBOX]="DESKTOP_BASE DESKTOP_WORK CODE TERMINAL HYPERLAND MUSIC" + [STEAMBOX]="GAME VR DESKTOP_BASE NVIDIA_GPU TERMINAL HYPERLAND" + [LABBOX]="DESKTOP_BASE CODE TERMINAL HYPERLAND LAB BT" + [SIMONBOX]="GAME VR DESKTOP_BASE NVIDIA_1080_GPU TERMINAL HYPERLAND BT" +) + +# Resolve the enabled roles for the provided host. Populates the global ROLES array. +roles::load() { + local host="$1" + declare -gA ROLES + for role in "${UPDATE_KNOWN_ROLES[@]}"; do + ROLES["$role"]="no" + done + + local enabled_roles="${ROLES_BY_HOST[$host]}" + if [[ -n "$enabled_roles" ]]; then + for role in $enabled_roles; do + ROLES["$role"]="yes" + done + fi +} + +# Helper to check if a role is enabled. +roles::enabled() { + local role="$1" + [[ "${ROLES[$role]:-no}" == "yes" ]] +} + +# Render a short summary of the enabled roles. +roles::print_summary() { + printf -- ' \033[37m' + for role in "${!ROLES[@]}"; do + if roles::enabled "$role"; then + printf '%s, ' "$role" + fi + done + printf -- '\n\n\033[37m' +} diff --git a/update/ubuntu.sh b/update/ubuntu.sh new file mode 100755 index 0000000..7482cc6 --- /dev/null +++ b/update/ubuntu.sh @@ -0,0 +1,457 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$SCRIPT_DIR/common.sh" + +ubuntu::print_banner() { + local version="$1" + local desktop="$2" + local wsl="$3" + printf -- ' \e[H\e[2J\n \e[0;31m .-.\e[0;37m\n \e[0;31m .-"``(|||)\e[0;37m\n \e[0;31m ,`\\ \\ `-`.\e[0;37m 88 88\n \e[0;31m / \\ "``-. `\e[0;37m 88 88\n \e[0;31m .-. , `___:\e[0;37m 88 88 88,888, 88 88 ,88888, 88888 88 88\n \e[0;31m (:::) : ___ \e[0;37m 88 88 88 88 88 88 88 88 88 88 88\n \e[0;31m `-` ` , :\e[0;37m 88 88 88 88 88 88 88 88 88 88 88\n \e[0;31m \\ / ,..-` ,\e[0;37m 88 88 88 88 88 88 88 88 88 88 88\n \e[0;31m `./ / .-.`\e[0;37m "88888" "88888" "88888" 88 88 "8888 "88888"\n \e[0;31m `-..-( )\e[0;37m\n \e[0;31m `-`\e[0;37m\n\n\n \e[1;32mCMtec Ubuntu install/Update script\e[0;37m\n \e[0;35mUbuntu version = '%s'\e[0;37m\n \e[0;35mDesktop = '%s'\e[0;37m\n \e[0;35mWSL = '%s'\e[0;37m\n' "$version" "$desktop" "$wsl" +} + +ubuntu::detect_environment() { + if [[ -f /etc/os-release ]]; then + . /etc/os-release + VER=$VERSION_ID + else + VER="0" + fi + + if [[ "$(dpkg -l | awk '/ubuntu-desktop/ {print }' | wc -l)" -ge 1 ]]; then + DESKTOP="yes" + else + DESKTOP="no" + fi + + if [[ "$(systemd-detect-virt)" == *"wsl"* ]]; then + WSL="yes" + else + WSL="no" + fi +} + +ubuntu::update_system() { + common::log_section "*** System applications ***" + + if [[ "$WSL" == "yes" ]]; then + common::log_step "Ubuntu 20.04 workaround" + sudo apt -y purge --auto-remove neovim + fi + + common::log_step "Updating system" + sudo apt update + sudo apt upgrade -y + if [[ "$WSL" == "no" ]]; then + if dpkg -s flatpak &>/dev/null; then + flatpak update -y + else + sudo add-apt-repository -y ppa:flatpak/stable + sudo apt update + sudo apt install -y flatpak + flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak install -y flathub com.github.tchx84.Flatseal + fi + fi + if dpkg -s snap &>/dev/null; then + sudo snap refresh + else + sudo apt install -y snap + fi +} + +ubuntu::install_system_packages() { + common::log_step "Installing system packages" + common::ensure_local_bin + sudo apt install -y gawk imagemagick gpg ninja-build gettext cmake unzip curl build-essential libssl-dev libffi-dev file libudev-dev pkg-config locales btop ncdu ranger timeshift + common::link_shell_profiles ubuntu + if [[ "$WSL" == "no" ]]; then + mkdir -p "$HOME/.config/alacritty" + common::link_alacritty_config + fi + common::link_update_shortcut + common::link_start_scripts +} + +ubuntu::install_homebrew() { + common::log_step "Installing Homebrew" + sudo apt-get install -y -qq build-essential curl file git + + export NONINTERACTIVE=1 + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + BREW_PREFIX="/home/linuxbrew/.linuxbrew" + if [[ -d "$BREW_PREFIX" ]]; then + eval "$("$BREW_PREFIX"/bin/brew shellenv)" + else + echo "[!] Homebrew install failed or brew not found at expected location." + exit 1 + fi + brew --version >/dev/null && echo "[✓] Homebrew installed successfully." +} + +ubuntu::setup_locales() { + common::log_step "Setup locals" + sudo locale-gen "en_US.UTF-8" + sudo update-locale LANG=en_US.UTF-8 +} + +ubuntu::install_rust() { + common::log_step "Installing Rust" + ( + cd "$HOME" + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path -y + ) + if [[ "$WSL" == "yes" ]]; then + # shellcheck disable=SC1091 + source "$HOME/.cargo/env" + fi + rustup self update + rustup update stable + rustup default stable +} + +ubuntu::install_python() { + common::log_step "Installing python" + sudo apt install -y python3 python3-pip python3-venv python3-dev pipx + if [[ "$VER" == "22.04" ]]; then + pip3 install pynvim + else + sudo apt install -y python3-pynvim + fi +} + +ubuntu::install_node() { + common::log_step "Installing node" + ( + cd "$HOME" + curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash - + ) + sudo apt install -y nodejs + npm config set prefix "${HOME}/.npm" +} + +ubuntu::install_tmux() { + common::log_step "Installing tmux" + sudo apt install -y libevent-dev ncurses-dev build-essential bison pkg-config + ( + cd "$HOME" + wget https://github.com/tmux/tmux/releases/download/3.3a/tmux-3.3a.tar.gz + tar xzvf tmux-3.3a.tar.gz + cd tmux-3.3a + ./configure + make && sudo make install + ) + common::link_tmux_conf +} + +ubuntu::install_virtualhere() { + common::install_virtualhere_client skip +} + +ubuntu::install_teensy_rules() { + if [[ ! -f /.dockerenv ]]; then + common::log_step "Installing teensy udev rules" + sudo rm -f /tmp/00-teensy.rules /etc/udev/rules.d/00-teensy.rules /lib/udev/rules.d/00-teensy.rules + sudo wget -O /tmp/00-teensy.rules https://www.pjrc.com/teensy/00-teensy.rules + sudo install -o root -g root -m 0664 /tmp/00-teensy.rules /lib/udev/rules.d/00-teensy.rules + sudo service udev restart + sudo udevadm control --reload-rules + sudo udevadm trigger + fi +} + +ubuntu::install_neovim() { + common::log_step "Installing neovim" + sudo apt install -y bat fd-find + ( + cd "$HOME" + if [[ -d "$HOME/neovim" ]]; then + rm -rf "$HOME/neovim" + fi + git clone https://github.com/neovim/neovim + cd neovim && git checkout stable + make CMAKE_BUILD_TYPE=RelWithDebInfo + cd build && cpack -G DEB && sudo dpkg -i nvim*.deb + rm -rf "$HOME/neovim" + ) + common::link_nvim_config +} + +ubuntu::install_caffeine() { + if [[ "$WSL" == "no" ]]; then + common::log_step "Installing caffeine" + sudo apt install -y caffeine + fi +} + +ubuntu::install_lazygit() { + common::log_step "Installing lazygit" + ( + cd "$HOME" + LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | grep -Po '"tag_name": "v\\K[^\"]*') + curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz" + tar xf lazygit.tar.gz lazygit + sudo install lazygit /usr/local/bin + rm "$HOME/lazygit" && rm "$HOME/lazygit.tar.gz" + ) + common::link_lazygit_config +} + +ubuntu::install_gdu() { + common::log_step "Installing gdu" + if [[ "$VER" == "22.04" ]]; then + sudo add-apt-repository -y ppa:daniel-milde/gdu + fi + sudo apt update + sudo apt install -y gdu +} + +ubuntu::install_ruby() { + common::log_step "Installing ruby" + sudo apt install -y ruby ruby-dev + sudo gem install neovim +} + +ubuntu::install_bottom() { + common::log_step "Installing bottom" + ( + cd "$HOME" + curl -LO https://github.com/ClementTsang/bottom/releases/download/0.9.1/bottom_0.9.1_amd64.deb + sudo dpkg -i bottom_0.9.1_amd64.deb + rm "$HOME/bottom_0.9.1_amd64.deb" + ) +} + +ubuntu::install_go() { + common::log_step "Installing go" + sudo apt install -y golang +} + +ubuntu::install_php() { + common::log_step "Installing php" + sudo apt install -y php php-curl php-xml php-mbstring +} + +ubuntu::install_luarocks() { + common::log_step "Installing luarocks" + sudo apt install -y luarocks +} + +ubuntu::install_composer() { + common::log_step "Installing composer" + sudo apt install -y composer +} + +ubuntu::install_java() { + common::log_step "Installing java" + if [[ "$WSL" == "yes" ]]; then + sudo add-apt-repository -y ppa:linuxuprising/java + sudo apt update + sudo apt install -y oracle-java17-installer + else + sudo apt install -y default-jdk + fi +} + +ubuntu::install_julia() { + common::log_step "Installing julia" + ( + cd "$HOME" + wget https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.0-linux-x86_64.tar.gz + tar -xvzf julia-1.9.0-linux-x86_64.tar.gz + sudo cp -r julia-1.9.0 /opt/ + sudo ln -sf /opt/julia-1.9.0/bin/julia /usr/local/bin/julia + rm julia-1.9.0-linux-x86_64.tar.gz* + rm -rf julia-1.9.0 + ) +} + +ubuntu::install_ble_sh() { + common::log_step "Installing ble.sh" + ( + cd "$HOME" + if [[ -d "$HOME/ble.sh" ]]; then + rm -rf "$HOME/ble.sh" + fi + git clone --recursive --depth 1 --shallow-submodules https://github.com/akinomyoga/ble.sh.git + make -C ble.sh install PREFIX=~/.local + rm -rf "$HOME/ble.sh" + ) + common::symlink "$PROJECT_ROOT/blerc" "$HOME/.blerc" +} + +ubuntu::install_platformio() { + common::log_step "Installing platformio" + if [[ "$VER" == "24.04" ]]; then + pipx install platformio + pipx ensurepath + else + pip3 install platformio + fi +} + +ubuntu::install_node_neovim() { + common::log_step "Installing neovim for node" + sudo npm install -g neovim +} + +ubuntu::install_rust_embedded() { + common::log_step "Installing Rust embedded stuff" + ( + cd "$HOME" + rustup target add thumbv6m-none-eabi + cargo install elf2uf2-rs --locked + cargo install probe-run + mkdir -p "$HOME/.local/bin" + common::symlink "$PROJECT_ROOT/pico-load.sh" "$HOME/.local/bin/pico-load" + ) +} + +ubuntu::install_cli_utilities() { + common::log_step "Installing tree sitter cli" + cargo install tree-sitter-cli + + common::log_step "Installing ripgrep" + cargo install ripgrep + + common::log_step "Installing eza" + cargo install eza + + common::log_step "Installing zoxide" + cargo install zoxide --locked + + common::log_step "Installing starship" + cargo install starship --locked + common::link_starship_config +} + +ubuntu::install_lsp_servers() { + common::log_step "Installing LSP servers" + rustup component add rust-analyzer clippy rustfmt + sudo npm i -g bash-language-server pyright + sudo npm install --save-dev prettier + sudo apt install -y shfmt clangd clang-format + pipx install black + pipx install isort + pipx install mdformat + brew install lua-language-server marksman + cargo install stylua + common::link_lua_check +} + +ubuntu::install_desktop_apps() { + if [[ "$WSL" == "no" ]]; then + common::log_section "*** Desktop applications ***" + + if [[ "$DESKTOP" == "yes" ]]; then + common::log_step "Installing Krita" + flatpak install -y flathub org.kde.krita + + common::log_step "Installing KiCad" + flatpak install -y flathub org.kicad.KiCad + + common::log_step "Installing FreeCad" + flatpak install -y flathub org.freecadweb.FreeCAD + + common::log_step "Installing Moonlight" + flatpak install -y flathub com.moonlight_stream.Moonlight + + common::log_step "Installing DrawIo" + flatpak install -y flathub com.jgraph.drawio.desktop + + common::log_step "Installing VSCode" + sudo snap install --classic code + + common::log_step "Installing gnome-shell-extension-manager" + sudo apt install -y gnome-shell-extension-manager + + common::log_step "Installing gnome tweaks" + sudo apt install -y gnome-tweaks + + common::log_step "Installing remmina" + sudo apt install -y remmina + + common::log_step "Installing feh" + sudo apt install -y feh + + common::log_step "Installing NerdFonts" + mkdir -p "/home/$USER/.local/share/fonts" + ( + cd "/home/$USER/.local/share/fonts" + wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/CodeNewRoman.zip + unzip -o CodeNewRoman.zip + rm CodeNewRoman.zip + wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/NerdFontsSymbolsOnly.zip + unzip -o NerdFontsSymbolsOnly.zip + rm NerdFontsSymbolsOnly.zip + fc-cache -fv + ) + + common::log_step "Installing Alacritty" + sudo add-apt-repository ppa:aslatter/ppa + sudo apt update + sudo apt install -y alacritty + + common::log_step "Installing Brave Browser" + sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg + echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main" | sudo tee /etc/apt/sources.list.d/brave-browser-release.list + sudo apt update + sudo apt install -y brave-browser + + if [[ "$VER" == "22.04" ]]; then + common::log_step "Installing Audio control" + sudo apt install -y pavucontrol + fi + if [[ "$VER" == "24.04" ]]; then + common::log_step "Installing Audio control" + sudo apt install -y pavucontrol helvum + fi + fi + fi +} + +ubuntu::final_message() { + printf -- '\033[32m \n\n***** Update complete! Please restart your terminal. *****\n\n\033[37m' +} + +ubuntu::run() { + sudo -v + ubuntu::detect_environment + + ubuntu::print_banner "$VER" "$DESKTOP" "$WSL" + + ubuntu::update_system + ubuntu::install_system_packages + ubuntu::install_homebrew + ubuntu::setup_locales + ubuntu::install_rust + ubuntu::install_python + ubuntu::install_node + common::install_fzf + ubuntu::install_tmux + ubuntu::install_virtualhere + ubuntu::install_teensy_rules + ubuntu::install_neovim + ubuntu::install_caffeine + ubuntu::install_lazygit + ubuntu::install_gdu + ubuntu::install_ruby + ubuntu::install_bottom + ubuntu::install_go + ubuntu::install_php + ubuntu::install_luarocks + ubuntu::install_composer + ubuntu::install_java + ubuntu::install_julia + ubuntu::install_ble_sh + ubuntu::install_platformio + ubuntu::install_node_neovim + ubuntu::install_rust_embedded + ubuntu::install_cli_utilities + ubuntu::install_lsp_servers + ubuntu::install_desktop_apps + + ubuntu::final_message +} diff --git a/update/update.sh b/update/update.sh new file mode 100755 index 0000000..5294491 --- /dev/null +++ b/update/update.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(dirname "$SCRIPT_DIR") + +source "$SCRIPT_DIR/common.sh" +source "$SCRIPT_DIR/arch.sh" +source "$SCRIPT_DIR/ubuntu.sh" + +update::detect_os() { + local name="" + if [[ -f /etc/os-release ]]; then + . /etc/os-release + name="$NAME" + fi + case "$name" in + "Arch Linux") echo "arch" ;; + "Ubuntu") echo "ubuntu" ;; + *) echo "" ;; + esac +} + +update::usage() { + cat <<'EOF' +Usage: update.sh [arch|ubuntu|auto] + +When no argument is provided, the script will attempt to detect the current distribution. +EOF +} + +update::main() { + local target="${1:-auto}" + local resolved="" + + case "$target" in + arch|ubuntu) + resolved="$target" + ;; + auto) + resolved=$(update::detect_os) + if [[ -z "$resolved" ]]; then + echo "[!] Unable to detect supported distribution." >&2 + exit 1 + fi + ;; + *) + update::usage + exit 1 + ;; + esac + + case "$resolved" in + arch) + arch::run + ;; + ubuntu) + ubuntu::run + ;; + esac +} + +update::main "$@"