norisa.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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"
  19. # TODO: fix manual install of wlogout
  20. fi
  21. # TODO: add element-desktop back
  22. 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 $ARCH_PKGS"
  23. readonly AUR_PKGS="redshift dashbinsh cspell-lsp doasedit-alternative nodejs-cspell nvim-lazy lexend-fonts-git xwaylandvideobridge jdtls gradle-autowrap localsend-bin $ARCH_AUR_PKGS"
  24. readonly C_RESET='\e[0m'
  25. readonly C_INFO='\e[1;36m' # Cyan
  26. readonly C_OK='\e[1;32m' # Green
  27. readonly C_CHANGE='\e[1;33m' # Yellow
  28. readonly C_ERR='\e[1;31m' # Red
  29. log_info() {
  30. echo -e "${C_INFO}[ INFO ]${C_RESET} $1"
  31. }
  32. log_ok() {
  33. echo -e "${C_OK}[ OK ]${C_RESET} $1"
  34. }
  35. log_changed() {
  36. echo -e "${C_CHANGE}[ CHANGE ]${C_RESET} $1"
  37. }
  38. log_error() {
  39. echo -e "${C_ERR}[ ERROR ]${C_RESET} $1" >&2
  40. }
  41. error_exit() {
  42. log_error "$1"
  43. exit 1
  44. }
  45. pkg_install_error_exit() {
  46. error_exit "Package installation command was not successfull. Exiting ..."
  47. }
  48. cd_error_exit() {
  49. log_info "Current working directory:"
  50. pwd
  51. error_exit "Could not change into '$1'. Exiting ..."
  52. }
  53. cd_into() {
  54. cd "$1" || cd_error_exit "$1"
  55. }
  56. ensure_pkgs_installed() {
  57. MISSING_PKGS="$(pacman -T $1)"
  58. log_info "Ensuring $2 packages are installed"
  59. if [ -n "$MISSING_PKGS" ]; then
  60. if [[ "$3" == *doas* ]]; then
  61. setup_temporary_doas
  62. fi
  63. $3 -Sy --noconfirm --needed $MISSING_PKGS || pkg_install_error_exit
  64. log_changed "$2 packages are now installed"
  65. else
  66. log_ok "$2 packages are already installed"
  67. fi
  68. }
  69. setup_temporary_doas() {
  70. # TODO: make more idempotent and DRY (setup_final_doas)
  71. log_changed "Setting up temporary doas config"
  72. printf "permit nopass :wheel
  73. permit nopass root as $username\n" >/etc/doas.conf
  74. chown -c root:root /etc/doas.conf
  75. chmod -c 0400 /etc/doas.conf
  76. }
  77. setup_final_doas() {
  78. log_info "Setting up final doas config"
  79. local config_path="/etc/doas.conf"
  80. local desired_content="permit persist :wheel
  81. permit nopass $username as root cmd mount
  82. permit nopass $username as root cmd umount
  83. permit nopass root as $username"
  84. if [[ -f "$config_path" ]] &&
  85. [[ "$(cat "$config_path")" == "$desired_content" ]] &&
  86. [[ "$(stat -c "%a %U:%G" "$config_path")" == "400 root:root" ]]; then
  87. log_ok "doas config is already in desired state"
  88. return 0
  89. fi
  90. printf "%s\n" "$desired_content" >"$config_path"
  91. chown root:root "$config_path"
  92. chmod 400 "$config_path"
  93. log_changed "Final doas config was set"
  94. }
  95. create_new_user() {
  96. echo -e "\e[0;30;42m Enter your desired username \e[0m"
  97. read -rp " >>> " username
  98. useradd -m -g users -G wheel "$username"
  99. log_changed "user '$username' was created"
  100. while true; do
  101. passwd "$username" && break
  102. done
  103. log_changed "password for user '$username' was set"
  104. }
  105. choose_user() {
  106. echo -e "\e[0;30;46m Available users: \e[0m"
  107. ls /home
  108. while true; do
  109. echo -e "\e[0;30;42m Enter in your chosen user \e[0m"
  110. read -rp " >>> " username
  111. ls /home/ | grep -q "^$username$" && break
  112. done
  113. }
  114. ensure_user_is_part_of_needed_groups() {
  115. log_info "Verify $username is part of video and input groups"
  116. if ! groups "$username" | grep "input" | grep -q "video"; then
  117. log_info "Adding $username to video and input groups"
  118. usermod -aG video "$username"
  119. usermod -aG input "$username"
  120. else
  121. log_ok "$username is already part of these groups"
  122. fi
  123. }
  124. ensure_history_file_exists() {
  125. log_info "Ensure history file exists"
  126. if ! [ -f /home/"$username"/.cache/zsh/history ]; then
  127. echo -e "\e[0;30;34mEnsuring initial zsh history file exists ...\e[0m"
  128. mkdir -vp /home/"$username"/.cache/zsh
  129. touch /home/"$username"/.cache/zsh/history
  130. log_changed "Created history file"
  131. else
  132. log_ok "history file is already present"
  133. fi
  134. }
  135. ensure_login_shell_is_zsh() {
  136. log_info "Ensure login shell is zsh"
  137. if ! grep "^$username.*::/home/$username" /etc/passwd | sed 's/^.*://' |
  138. grep -q "^$(which zsh)$"; then
  139. echo -e "\e[0;30;34mSetting default shell to $(which zsh)...\e[0m"
  140. chsh -s "$(which zsh)" "$username" || exit 1
  141. log_changed "changed shell to zsh"
  142. else
  143. log_ok "login shell is already zsh"
  144. fi
  145. }
  146. ensure_user_selected() {
  147. if [ -d /home ]; then
  148. mapfile -t home_users < <(ls -A /home)
  149. user_count=${#home_users[@]}
  150. else
  151. user_count=0
  152. fi
  153. if [ "$user_count" -eq 1 ]; then
  154. username="${home_users[0]}"
  155. echo -e "\e[0;30;46m A single user was found: $username \e[0m"
  156. elif [ "$user_count" -gt 1 ]; then
  157. echo -e "\e[0;30;46m /home/ not empty, $user_count users already available \e[0m"
  158. while true; do
  159. echo -e "\e[0;30;42m Do you want to create another user? [y/n] \e[0m"
  160. read -rp " >>> " want_new_user
  161. if [[ "$want_new_user" =~ ^[yY]$ ]]; then
  162. create_new_user
  163. break
  164. elif [[ "$want_new_user" =~ ^[nN]$ ]]; then
  165. choose_user
  166. break
  167. fi
  168. done
  169. else
  170. want_new_user=y
  171. create_new_user
  172. fi
  173. }
  174. ensure_needed_dirs_created() {
  175. log_info "Creating needed ~/ directories"
  176. needed_dirs=(
  177. "/home/$username/dox"
  178. "/home/$username/pix"
  179. "/home/$username/dl"
  180. "/home/$username/vids"
  181. "/home/$username/mus"
  182. "/home/$username/.local/bin"
  183. "/home/$username/.config"
  184. "/home/$username/.local/share"
  185. "/home/$username/.local/src"
  186. "/home/$username/.local/"
  187. )
  188. if ! chown -v "$username:users" "${needed_dirs[@]}" >/dev/null; then
  189. mkdir -Rvp "${needed_dirs[@]}"
  190. log_changed "Created needed ~/ directories"
  191. else
  192. log_ok "Needed ~/ directories are already present"
  193. fi
  194. }
  195. ensure_sudo_is_symlinked_to_doas() {
  196. log_info "Ensure sudo is symlinked to doas"
  197. if [ ! -f /usr/bin/sudo ]; then
  198. ln -s /usr/bin/doas /usr/bin/sudo
  199. log_changed "sudo was symlinked to doas"
  200. else
  201. log_ok "sudo is already symlinked to doas"
  202. fi
  203. }
  204. # add xdg-repo
  205. # if ! grep -q "^\s*\[xdg-repo\]\s*$" /etc/pacman.conf; then
  206. # echo -e "\e[0;30;34mAdding Noah's xdg-repo ...\e[0m"
  207. # pacman-key --recv-keys 7FA7BB604F2A4346 --keyserver keyserver.ubuntu.com
  208. # pacman-key --lsign-key 7FA7BB604F2A4346
  209. # echo "[xdg-repo]
  210. # Server = https://git.noahvogt.com/noah/\$repo/raw/master/\$arch" >> /etc/pacman.conf
  211. # fi
  212. ensure_chaotic_aur_installed() {
  213. if [ "$ARCH" = "x86_64" ]; then
  214. if ! grep -q "^\s*\[chaotic-aur\]\s*$" /etc/pacman.conf; then
  215. echo -e "\e[0;30;34mAdding the chaotic aur repo ...\e[0m"
  216. pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com
  217. pacman-key --lsign-key 3056513887B78AEB
  218. pacman -U --noconfirm 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst'
  219. pacman -U --noconfirm 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst'
  220. echo "[chaotic-aur]
  221. Include = /etc/pacman.d/chaotic-mirrorlist" >>/etc/pacman.conf
  222. fi
  223. fi
  224. }
  225. # Install AUR Helper (paru as paru-bin is out-of-date)
  226. ensure_paru_installed() {
  227. log_info "Ensuring paru is installed"
  228. if ! command -v paru >/dev/null 2>&1; then
  229. if [ "$ARCH" = "x86_64" ] && pacman -Si paru >/dev/null 2>&1; then
  230. pacman -S --noconfirm paru
  231. else
  232. setup_temporary_doas
  233. log_info "Building paru from source..."
  234. temp_dir=$(mktemp -d)
  235. chown "$username:users" "$temp_dir"
  236. doas -u "$username" bash -c "cd $temp_dir && git clone https://aur.archlinux.org/paru.git && cd paru && makepkg --noconfirm"
  237. pacman -U --noconfirm "$temp_dir"/paru/*.pkg.tar.* || pkg_install_error_exit
  238. fi
  239. log_changed "Installed AUR helper (paru)"
  240. else
  241. log_ok "AUR helper (paru) is already installed"
  242. fi
  243. }
  244. ensure_global_zsh_installed() {
  245. log_info "Ensuring global zshenv"
  246. if grep -q "export ZDOTDIR=\$HOME/.config/zsh" /etc/zsh/zshenv; then
  247. log_ok "Global zshenv ist already installed"
  248. else
  249. mkdir -vp /etc/zsh
  250. echo "export ZDOTDIR=\$HOME/.config/zsh" >/etc/zsh/zshenv
  251. log_changed "Installed global zshenv"
  252. fi
  253. }
  254. ensure_bluetooth_service_enabled() {
  255. log_info "Ensuring Bluetooth service is enabled"
  256. if ! systemctl is-enabled bluetooth.service >/dev/null 2>&1; then
  257. systemctl enable bluetooth.service
  258. log_changed "Enabled bluetooth.service system-wide"
  259. else
  260. log_ok "Bluetooth service is already enabled"
  261. fi
  262. }
  263. ensure_history_file_not_present() {
  264. if [ -f "$1" ]; then
  265. rm "$1"
  266. log_changed "$2 history file was removed"
  267. else
  268. log_ok "No $2 history file is present"
  269. fi
  270. }
  271. cleanup_home() {
  272. log_info "Cleaning up \$HOME"
  273. local bash_history="$username/.bash_history"
  274. local less_history="$username/.lesshst"
  275. ensure_history_file_not_present "$bash_history" bash
  276. ensure_history_file_not_present "$less_history" less
  277. }
  278. ensure_dns_priority_in_nsswitch() {
  279. log_info "Ensuring DNS priority in /etc/nsswitch.conf"
  280. if grep -q "hosts:.*dns.*resolve" /etc/nsswitch.conf; then
  281. log_ok "DNS priority is already correct in nsswitch.conf"
  282. else
  283. cp /etc/nsswitch.conf /etc/nsswitch.conf.bak
  284. 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"
  285. log_changed "Updated hosts config in nsswitch.conf"
  286. fi
  287. }
  288. ensure_dotfiles_are_fetched_and_applied() {
  289. log_info "Ensuring dotfiles are fetched and applied"
  290. if [ ! -d /home/"$username"/.local/src/dotfiles ]; then
  291. echo -e "\e[0;30;34mFetching dotfiles ...\e[0m"
  292. cd_into /home/"$username"/.local/src
  293. git clone https://git.noahvogt.com/noah/dotfiles.git || error_exit "Failed to clone dotfiles git repository"
  294. cd_into /home/"$username"/.local/src/dotfiles
  295. setup_temporary_doas
  296. doas -u "$username" /home/"$username"/.local/src/dotfiles/apply-dotfiles
  297. log_changed "dotfiles were fetched and applied successfully"
  298. else
  299. log_ok "dotfiles were already fetched"
  300. fi
  301. }
  302. ensure_pkgs_installed "$BASE_PKGS" "some basic" "pacman"
  303. ensure_user_selected
  304. ensure_needed_dirs_created
  305. ensure_user_is_part_of_needed_groups
  306. ensure_sudo_is_symlinked_to_doas
  307. ensure_chaotic_aur_installed
  308. ensure_paru_installed
  309. ensure_pkgs_installed "$MAIN_PKGS" "main packages" "pacman"
  310. ensure_pkgs_installed "$AUR_PKGS" "AUR" "doas -u $username paru"
  311. ensure_dotfiles_are_fetched_and_applied
  312. ensure_global_zsh_installed
  313. ensure_history_file_exists
  314. ensure_login_shell_is_zsh
  315. setup_final_doas
  316. ensure_bluetooth_service_enabled
  317. ensure_dns_priority_in_nsswitch
  318. cleanup_home