norisa.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. #!/bin/bash
  2. # SPDX-License-Identifier: GPL-3.0-or-later
  3. # ASSUMED STATE OF TARGET SYSTEM:
  4. # - internet access
  5. # - root user login
  6. # - ~30 GB of free disk space
  7. # working 1.) base 2.) linux packages
  8. # Install opendoas and (base-devel, devtools minus sudo), libxft, cargo
  9. readonly BASE_PKGS="archlinux-keyring opendoas autoconf automake binutils bison debugedit fakeroot file findutils flex gawk gcc gettext grep groff gzip libtool m4 make pacman patch pkgconf sed texinfo which libxft breezy coreutils curl diffutils expac git glow gum jq mercurial openssh parallel reuse rsync subversion util-linux cargo"
  10. # Architecture-specific packages
  11. ARCH=$(uname -m)
  12. if [ "$ARCH" = "x86_64" ]; then
  13. ARCH_PKGS="xf86-video-vesa xf86-video-fbdev xf86-video-amdgpu xf86-video-intel xf86-video-nouveau ungoogled-chromium-bin obs-studio brave-bin ghostty ttf-material-symbols-variable-git nomacs wlogout unifetch shellcheck yt-dlp"
  14. ARCH_AUR_PKGS="simple-mtpfs google-java-format code2prompt-bin"
  15. else
  16. # Asahi/ARM specific or generic alternatives
  17. ARCH_PKGS="chromium"
  18. ARCH_AUR_PKGS="unifetch shellcheck-bin yt-dlp-git logseq-desktop-bin code2prompt wlogout"
  19. fi
  20. readonly MAIN_PKGS="xorg-server neovim ranger xournalpp ffmpeg sxiv arandr man-db brightnessctl unzip python mupdf-gl mediainfo highlight pipewire pipewire-pulse pipewire-alsa pipewire-audio wireplumber pulsemixer pamixer ttf-linux-libertine calcurse xclip noto-fonts-emoji imagemagick gimp xorg-setxkbmap wavemon dash htop wireless_tools alsa-utils acpi zip libreoffice-fresh nm-connection-editor dunst libnotify dosfstools mpv xorg-xinput cpupower zsh zsh-syntax-highlighting newsboat pcmanfm openbsd-netcat powertop mupdf-tools stow zsh-autosuggestions npm fzf unclutter mpd mpc ncmpcpp pavucontrol strawberry smartmontools firefox python-pynvim python-pylint tesseract-data-deu tesseract-data-eng keepassxc img2pdf dust ctags python-wand python-termcolor python-black jdk-openjdk ripgrep lf ttf-jetbrains-mono-nerd foliate coreutils curl fish foot fuzzel gjs gnome-bluetooth-3.0 gnome-control-center gnome-keyring gobject-introspection grim gtk3 gtk-layer-shell libdbusmenu-gtk3 meson nlohmann-json plasma-browser-integration playerctl polkit-gnome python-pywal sassc slurp swayidle typescript xorg-xrandr webp-pixbuf-loader yad hyprland python-poetry python-build python-pillow ttf-space-mono-nerd kitty shfmt ruff luarocks rust-analyzer hyprland-guiutils waybar socat hyprlock clang swaync bat wl-clipboard syncthing python-debugpy awww kitty tokei gemini-cli hypridle tlp texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks texlive-publishers texlive-xetex libva-utils blueman woff2-font-awesome bind qt5-wayland qt6-wayland pre-commit python-pandas pyright python-beautifulsoup4 tree-sitter-cli jupyterlab python-httplib2 jdk11-openjdk zathura-pdf-mupdf imv rclone openconnect python-evdev tree python-seaborn mlocate fastfetch sqlx-cli biber $ARCH_PKGS"
  21. readonly AUR_PKGS="redshift dashbinsh cspell-lsp doasedit-alternative nodejs-cspell nvim-lazy lexend-fonts-git xwaylandvideobridge jdtls gradle-autowrap localsend-bin python-sklearn-onnx $ARCH_AUR_PKGS"
  22. readonly C_RESET='\e[0m'
  23. readonly C_INFO='\e[1;36m' # Cyan
  24. readonly C_OK='\e[1;32m' # Green
  25. readonly C_CHANGE='\e[1;33m' # Yellow
  26. readonly C_ERR='\e[1;31m' # Red
  27. log_info() {
  28. echo -e "${C_INFO}[ INFO ]${C_RESET} $1"
  29. }
  30. log_ok() {
  31. echo -e "${C_OK}[ OK ]${C_RESET} $1"
  32. }
  33. log_changed() {
  34. echo -e "${C_CHANGE}[ CHANGE ]${C_RESET} $1"
  35. }
  36. log_error() {
  37. echo -e "${C_ERR}[ ERROR ]${C_RESET} $1" >&2
  38. }
  39. error_exit() {
  40. log_error "$1"
  41. exit 1
  42. }
  43. pkg_install_error_exit() {
  44. error_exit "Package installation command was not successfull. Exiting ..."
  45. }
  46. cd_error_exit() {
  47. log_info "Current working directory:"
  48. pwd
  49. error_exit "Could not change into '$1'. Exiting ..."
  50. }
  51. cd_into() {
  52. cd "$1" || cd_error_exit "$1"
  53. }
  54. ensure_pkgs_installed() {
  55. MISSING_PKGS="$(pacman -T $1)"
  56. log_info "Ensuring $2 packages are installed"
  57. if [ -n "$MISSING_PKGS" ]; then
  58. if [[ "$3" == *doas* ]]; then
  59. setup_temporary_doas
  60. fi
  61. $3 -Sy --noconfirm --needed $MISSING_PKGS || pkg_install_error_exit
  62. log_changed "$2 packages are now installed"
  63. else
  64. log_ok "$2 packages are already installed"
  65. fi
  66. }
  67. _write_doas_config() {
  68. local content="$1"
  69. local config_path="/etc/doas.conf"
  70. if [[ -f "$config_path" ]] &&
  71. [[ "$(cat "$config_path")" == "$content" ]] &&
  72. [[ "$(stat -c "%a %U:%G" "$config_path")" == "400 root:root" ]]; then
  73. return 1 # no change needed
  74. fi
  75. printf "%s\n" "$content" >"$config_path"
  76. chown root:root "$config_path"
  77. chmod 400 "$config_path"
  78. return 0 # changed
  79. }
  80. setup_temporary_doas() {
  81. log_info "Setting up temporary doas config"
  82. local content="permit nopass :wheel
  83. permit nopass root as $username"
  84. if _write_doas_config "$content"; then
  85. log_changed "Temporary doas config was set"
  86. else
  87. log_ok "doas config is already in desired state"
  88. fi
  89. }
  90. setup_final_doas() {
  91. log_info "Setting up final doas config"
  92. local content="permit persist :wheel
  93. permit nopass $username as root cmd mount
  94. permit nopass $username as root cmd umount
  95. permit nopass root as $username"
  96. if _write_doas_config "$content"; then
  97. log_changed "Final doas config was set"
  98. else
  99. log_ok "doas config is already in desired state"
  100. fi
  101. }
  102. create_new_user() {
  103. echo -e "\e[0;30;42m Enter your desired username \e[0m"
  104. read -rp " >>> " username
  105. useradd -m -g users -G wheel "$username"
  106. log_changed "user '$username' was created"
  107. while true; do
  108. passwd "$username" && break
  109. done
  110. log_changed "password for user '$username' was set"
  111. }
  112. choose_user() {
  113. echo -e "\e[0;30;46m Available users: \e[0m"
  114. ls /home
  115. while true; do
  116. echo -e "\e[0;30;42m Enter in your chosen user \e[0m"
  117. read -rp " >>> " username
  118. ls /home/ | grep -q "^$username$" && break
  119. done
  120. }
  121. ensure_user_is_part_of_needed_groups() {
  122. log_info "Verify $username is part of video and input groups"
  123. if ! groups "$username" | grep "input" | grep -q "video"; then
  124. log_info "Adding $username to video and input groups"
  125. usermod -aG video "$username"
  126. usermod -aG input "$username"
  127. else
  128. log_ok "$username is already part of these groups"
  129. fi
  130. }
  131. ensure_history_file_exists() {
  132. log_info "Ensure history file exists"
  133. if ! [ -f /home/"$username"/.cache/zsh/history ]; then
  134. echo -e "\e[0;30;34mEnsuring initial zsh history file exists ...\e[0m"
  135. mkdir -vp /home/"$username"/.cache/zsh
  136. touch /home/"$username"/.cache/zsh/history
  137. log_changed "Created history file"
  138. else
  139. log_ok "history file is already present"
  140. fi
  141. }
  142. ensure_login_shell_is_zsh() {
  143. log_info "Ensure login shell is zsh"
  144. if ! grep "^$username.*::/home/$username" /etc/passwd | sed 's/^.*://' |
  145. grep -q "^$(which zsh)$"; then
  146. echo -e "\e[0;30;34mSetting default shell to $(which zsh)...\e[0m"
  147. chsh -s "$(which zsh)" "$username" || exit 1
  148. log_changed "changed shell to zsh"
  149. else
  150. log_ok "login shell is already zsh"
  151. fi
  152. }
  153. ensure_user_selected() {
  154. if [ -d /home ]; then
  155. mapfile -t home_users < <(ls -A /home)
  156. user_count=${#home_users[@]}
  157. else
  158. user_count=0
  159. fi
  160. if [ "$user_count" -eq 1 ]; then
  161. username="${home_users[0]}"
  162. echo -e "\e[0;30;46m A single user was found: $username \e[0m"
  163. elif [ "$user_count" -gt 1 ]; then
  164. echo -e "\e[0;30;46m /home/ not empty, $user_count users already available \e[0m"
  165. while true; do
  166. echo -e "\e[0;30;42m Do you want to create another user? [y/n] \e[0m"
  167. read -rp " >>> " want_new_user
  168. if [[ "$want_new_user" =~ ^[yY]$ ]]; then
  169. create_new_user
  170. break
  171. elif [[ "$want_new_user" =~ ^[nN]$ ]]; then
  172. choose_user
  173. break
  174. fi
  175. done
  176. else
  177. want_new_user=y
  178. create_new_user
  179. fi
  180. }
  181. ensure_needed_dirs_created() {
  182. log_info "Creating needed ~/ directories"
  183. needed_dirs=(
  184. "/home/$username/dox"
  185. "/home/$username/pix"
  186. "/home/$username/dl"
  187. "/home/$username/vids"
  188. "/home/$username/mus"
  189. "/home/$username/.local/bin"
  190. "/home/$username/.config"
  191. "/home/$username/.local/share"
  192. "/home/$username/.local/src"
  193. "/home/$username/.local/"
  194. )
  195. if ! chown -v "$username:users" "${needed_dirs[@]}" >/dev/null; then
  196. mkdir -Rvp "${needed_dirs[@]}"
  197. log_changed "Created needed ~/ directories"
  198. else
  199. log_ok "Needed ~/ directories are already present"
  200. fi
  201. }
  202. ensure_sudo_is_symlinked_to_doas() {
  203. log_info "Ensure sudo is symlinked to doas"
  204. if [ ! -f /usr/bin/sudo ]; then
  205. ln -s /usr/bin/doas /usr/bin/sudo
  206. log_changed "sudo was symlinked to doas"
  207. else
  208. log_ok "sudo is already symlinked to doas"
  209. fi
  210. }
  211. # add xdg-repo
  212. # if ! grep -q "^\s*\[xdg-repo\]\s*$" /etc/pacman.conf; then
  213. # echo -e "\e[0;30;34mAdding Noah's xdg-repo ...\e[0m"
  214. # pacman-key --recv-keys 7FA7BB604F2A4346 --keyserver keyserver.ubuntu.com
  215. # pacman-key --lsign-key 7FA7BB604F2A4346
  216. # echo "[xdg-repo]
  217. # Server = https://git.noahvogt.com/noah/\$repo/raw/master/\$arch" >> /etc/pacman.conf
  218. # fi
  219. ensure_chaotic_aur_installed() {
  220. if [ "$ARCH" = "x86_64" ]; then
  221. if ! grep -q "^\s*\[chaotic-aur\]\s*$" /etc/pacman.conf; then
  222. echo -e "\e[0;30;34mAdding the chaotic aur repo ...\e[0m"
  223. pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com
  224. pacman-key --lsign-key 3056513887B78AEB
  225. pacman -U --noconfirm 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst'
  226. pacman -U --noconfirm 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst'
  227. echo "[chaotic-aur]
  228. Include = /etc/pacman.d/chaotic-mirrorlist" >>/etc/pacman.conf
  229. fi
  230. fi
  231. }
  232. # Install AUR Helper (paru as paru-bin is out-of-date)
  233. ensure_paru_installed() {
  234. log_info "Ensuring paru is installed"
  235. if ! command -v paru >/dev/null 2>&1; then
  236. if [ "$ARCH" = "x86_64" ] && pacman -Si paru >/dev/null 2>&1; then
  237. pacman -S --noconfirm paru
  238. else
  239. setup_temporary_doas
  240. log_info "Building paru from source..."
  241. temp_dir=$(mktemp -d)
  242. chown "$username:users" "$temp_dir"
  243. doas -u "$username" bash -c "cd $temp_dir && git clone https://aur.archlinux.org/paru.git && cd paru && makepkg --noconfirm"
  244. pacman -U --noconfirm "$temp_dir"/paru/*.pkg.tar.* || pkg_install_error_exit
  245. fi
  246. log_changed "Installed AUR helper (paru)"
  247. else
  248. log_ok "AUR helper (paru) is already installed"
  249. fi
  250. }
  251. ensure_global_zsh_installed() {
  252. log_info "Ensuring global zshenv"
  253. if grep -q "export ZDOTDIR=\$HOME/.config/zsh" /etc/zsh/zshenv; then
  254. log_ok "Global zshenv ist already installed"
  255. else
  256. mkdir -vp /etc/zsh
  257. echo "export ZDOTDIR=\$HOME/.config/zsh" >/etc/zsh/zshenv
  258. log_changed "Installed global zshenv"
  259. fi
  260. }
  261. ensure_bluetooth_service_enabled() {
  262. log_info "Ensuring Bluetooth service is enabled"
  263. if ! systemctl is-enabled bluetooth.service >/dev/null 2>&1; then
  264. systemctl enable bluetooth.service
  265. log_changed "Enabled bluetooth.service system-wide"
  266. else
  267. log_ok "Bluetooth service is already enabled"
  268. fi
  269. }
  270. ensure_history_file_not_present() {
  271. if [ -f "$1" ]; then
  272. rm "$1"
  273. log_changed "$2 history file was removed"
  274. else
  275. log_ok "No $2 history file is present"
  276. fi
  277. }
  278. cleanup_home() {
  279. log_info "Cleaning up \$HOME"
  280. local bash_history="$username/.bash_history"
  281. local less_history="$username/.lesshst"
  282. ensure_history_file_not_present "$bash_history" bash
  283. ensure_history_file_not_present "$less_history" less
  284. }
  285. ensure_dns_priority_in_nsswitch() {
  286. log_info "Ensuring DNS priority in /etc/nsswitch.conf"
  287. if grep -q "hosts:.*dns.*resolve" /etc/nsswitch.conf; then
  288. log_ok "DNS priority is already correct in nsswitch.conf"
  289. else
  290. cp /etc/nsswitch.conf /etc/nsswitch.conf.bak
  291. sed -i 's/^hosts:.*/hosts: mymachines files dns resolve [!UNAVAIL=return] myhostname/' /etc/nsswitch.conf || error_exit "Failed to set new config options on /etc/nsswitch.conf"
  292. log_changed "Updated hosts config in nsswitch.conf"
  293. fi
  294. }
  295. ensure_dotfiles_are_fetched_and_applied() {
  296. log_info "Ensuring dotfiles are fetched and applied"
  297. if [ ! -d /home/"$username"/.local/src/dotfiles ]; then
  298. echo -e "\e[0;30;34mFetching dotfiles ...\e[0m"
  299. cd_into /home/"$username"/.local/src
  300. git clone https://git.noahvogt.com/noah/dotfiles.git || error_exit "Failed to clone dotfiles git repository"
  301. cd_into /home/"$username"/.local/src/dotfiles
  302. setup_temporary_doas
  303. doas -u "$username" /home/"$username"/.local/src/dotfiles/apply-dotfiles
  304. log_changed "dotfiles were fetched and applied successfully"
  305. else
  306. log_ok "dotfiles were already fetched"
  307. fi
  308. }
  309. ensure_pkgs_installed "$BASE_PKGS" "some basic" "pacman"
  310. ensure_user_selected
  311. ensure_needed_dirs_created
  312. ensure_user_is_part_of_needed_groups
  313. ensure_sudo_is_symlinked_to_doas
  314. ensure_chaotic_aur_installed
  315. ensure_paru_installed
  316. ensure_pkgs_installed "$MAIN_PKGS" "main packages" "pacman"
  317. ensure_pkgs_installed "$AUR_PKGS" "AUR" "doas -u $username paru --mflags --ignorearch"
  318. ensure_dotfiles_are_fetched_and_applied
  319. ensure_global_zsh_installed
  320. ensure_history_file_exists
  321. ensure_login_shell_is_zsh
  322. setup_final_doas
  323. ensure_bluetooth_service_enabled
  324. ensure_dns_priority_in_nsswitch
  325. cleanup_home