norisa.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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 texlive-langgerman wget docker-compose docker-buildx gnome-connections $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 kotlin-language-server-bin ktlint-compose-rules ktlint pup $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_user_is_part_of_docker_group() {
  132. log_info "Verify $username is part of docker group"
  133. if ! groups "$username" | grep "docker"; then
  134. log_info "Adding $username to docker group"
  135. usermod -aG docker "$username"
  136. else
  137. log_ok "$username is already part of the docker group"
  138. fi
  139. }
  140. ensure_history_file_exists() {
  141. log_info "Ensure history file exists"
  142. if ! [ -f /home/"$username"/.cache/zsh/history ]; then
  143. echo -e "\e[0;30;34mEnsuring initial zsh history file exists ...\e[0m"
  144. mkdir -vp /home/"$username"/.cache/zsh
  145. touch /home/"$username"/.cache/zsh/history
  146. log_changed "Created history file"
  147. else
  148. log_ok "history file is already present"
  149. fi
  150. }
  151. ensure_login_shell_is_zsh() {
  152. log_info "Ensure login shell is zsh"
  153. if ! grep "^$username.*::/home/$username" /etc/passwd | sed 's/^.*://' |
  154. grep -q "^$(which zsh)$"; then
  155. echo -e "\e[0;30;34mSetting default shell to $(which zsh)...\e[0m"
  156. chsh -s "$(which zsh)" "$username" || exit 1
  157. log_changed "changed shell to zsh"
  158. else
  159. log_ok "login shell is already zsh"
  160. fi
  161. }
  162. ensure_user_selected() {
  163. if [ -d /home ]; then
  164. mapfile -t home_users < <(ls -A /home)
  165. user_count=${#home_users[@]}
  166. else
  167. user_count=0
  168. fi
  169. if [ "$user_count" -eq 1 ]; then
  170. username="${home_users[0]}"
  171. echo -e "\e[0;30;46m A single user was found: $username \e[0m"
  172. elif [ "$user_count" -gt 1 ]; then
  173. echo -e "\e[0;30;46m /home/ not empty, $user_count users already available \e[0m"
  174. while true; do
  175. echo -e "\e[0;30;42m Do you want to create another user? [y/n] \e[0m"
  176. read -rp " >>> " want_new_user
  177. if [[ "$want_new_user" =~ ^[yY]$ ]]; then
  178. create_new_user
  179. break
  180. elif [[ "$want_new_user" =~ ^[nN]$ ]]; then
  181. choose_user
  182. break
  183. fi
  184. done
  185. else
  186. want_new_user=y
  187. create_new_user
  188. fi
  189. }
  190. ensure_needed_dirs_created() {
  191. log_info "Creating needed ~/ directories"
  192. needed_dirs=(
  193. "/home/$username/dox"
  194. "/home/$username/pix"
  195. "/home/$username/dl"
  196. "/home/$username/vids"
  197. "/home/$username/mus"
  198. "/home/$username/.local/bin"
  199. "/home/$username/.config"
  200. "/home/$username/.local/share"
  201. "/home/$username/.local/src"
  202. "/home/$username/.local/"
  203. )
  204. if ! chown -v "$username:users" "${needed_dirs[@]}" >/dev/null; then
  205. mkdir -Rvp "${needed_dirs[@]}"
  206. log_changed "Created needed ~/ directories"
  207. else
  208. log_ok "Needed ~/ directories are already present"
  209. fi
  210. }
  211. ensure_sudo_is_symlinked_to_doas() {
  212. log_info "Ensure sudo is symlinked to doas"
  213. if [ ! -f /usr/bin/sudo ]; then
  214. ln -s /usr/bin/doas /usr/bin/sudo
  215. log_changed "sudo was symlinked to doas"
  216. else
  217. log_ok "sudo is already symlinked to doas"
  218. fi
  219. }
  220. # add xdg-repo
  221. # if ! grep -q "^\s*\[xdg-repo\]\s*$" /etc/pacman.conf; then
  222. # echo -e "\e[0;30;34mAdding Noah's xdg-repo ...\e[0m"
  223. # pacman-key --recv-keys 7FA7BB604F2A4346 --keyserver keyserver.ubuntu.com
  224. # pacman-key --lsign-key 7FA7BB604F2A4346
  225. # echo "[xdg-repo]
  226. # Server = https://git.noahvogt.com/noah/\$repo/raw/master/\$arch" >> /etc/pacman.conf
  227. # fi
  228. ensure_chaotic_aur_installed() {
  229. if [ "$ARCH" = "x86_64" ]; then
  230. if ! grep -q "^\s*\[chaotic-aur\]\s*$" /etc/pacman.conf; then
  231. echo -e "\e[0;30;34mAdding the chaotic aur repo ...\e[0m"
  232. pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com
  233. pacman-key --lsign-key 3056513887B78AEB
  234. pacman -U --noconfirm 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst'
  235. pacman -U --noconfirm 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst'
  236. echo "[chaotic-aur]
  237. Include = /etc/pacman.d/chaotic-mirrorlist" >>/etc/pacman.conf
  238. fi
  239. fi
  240. }
  241. # Install AUR Helper (paru as paru-bin is out-of-date)
  242. ensure_paru_installed() {
  243. log_info "Ensuring paru is installed"
  244. if ! command -v paru >/dev/null 2>&1; then
  245. if [ "$ARCH" = "x86_64" ] && pacman -Si paru >/dev/null 2>&1; then
  246. pacman -S --noconfirm paru
  247. else
  248. setup_temporary_doas
  249. log_info "Building paru from source..."
  250. temp_dir=$(mktemp -d)
  251. chown "$username:users" "$temp_dir"
  252. doas -u "$username" bash -c "cd $temp_dir && git clone https://aur.archlinux.org/paru.git && cd paru && makepkg --noconfirm"
  253. pacman -U --noconfirm "$temp_dir"/paru/*.pkg.tar.* || pkg_install_error_exit
  254. fi
  255. log_changed "Installed AUR helper (paru)"
  256. else
  257. log_ok "AUR helper (paru) is already installed"
  258. fi
  259. }
  260. ensure_global_zsh_installed() {
  261. log_info "Ensuring global zshenv"
  262. if grep -q "export ZDOTDIR=\$HOME/.config/zsh" /etc/zsh/zshenv; then
  263. log_ok "Global zshenv ist already installed"
  264. else
  265. mkdir -vp /etc/zsh
  266. echo "export ZDOTDIR=\$HOME/.config/zsh" >/etc/zsh/zshenv
  267. log_changed "Installed global zshenv"
  268. fi
  269. }
  270. ensure_bluetooth_service_enabled() {
  271. log_info "Ensuring Bluetooth service is enabled"
  272. if ! systemctl is-enabled bluetooth.service >/dev/null 2>&1; then
  273. systemctl enable bluetooth.service
  274. log_changed "Enabled bluetooth.service system-wide"
  275. else
  276. log_ok "Bluetooth service is already enabled"
  277. fi
  278. }
  279. ensure_docker_service_enabled() {
  280. log_info "Ensuring Docker service is enabled"
  281. if ! systemctl is-enabled docker.service >/dev/null 2>&1; then
  282. systemctl enable docker.service
  283. log_changed "Enabled docker.service system-wide"
  284. else
  285. log_ok "Docker service is already enabled"
  286. fi
  287. }
  288. ensure_history_file_not_present() {
  289. if [ -f "$1" ]; then
  290. rm "$1"
  291. log_changed "$2 history file was removed"
  292. else
  293. log_ok "No $2 history file is present"
  294. fi
  295. }
  296. cleanup_home() {
  297. log_info "Cleaning up \$HOME"
  298. local bash_history="$username/.bash_history"
  299. local less_history="$username/.lesshst"
  300. ensure_history_file_not_present "$bash_history" bash
  301. ensure_history_file_not_present "$less_history" less
  302. }
  303. ensure_dns_priority_in_nsswitch() {
  304. log_info "Ensuring DNS priority in /etc/nsswitch.conf"
  305. if grep -q "hosts:.*dns.*resolve" /etc/nsswitch.conf; then
  306. log_ok "DNS priority is already correct in nsswitch.conf"
  307. else
  308. cp /etc/nsswitch.conf /etc/nsswitch.conf.bak
  309. 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"
  310. log_changed "Updated hosts config in nsswitch.conf"
  311. fi
  312. }
  313. ensure_dotfiles_are_fetched_and_applied() {
  314. log_info "Ensuring dotfiles are fetched and applied"
  315. if [ ! -d /home/"$username"/.local/src/dotfiles ]; then
  316. echo -e "\e[0;30;34mFetching dotfiles ...\e[0m"
  317. cd_into /home/"$username"/.local/src
  318. git clone https://git.noahvogt.com/noah/dotfiles.git || error_exit "Failed to clone dotfiles git repository"
  319. cd_into /home/"$username"/.local/src/dotfiles
  320. setup_temporary_doas
  321. doas -u "$username" /home/"$username"/.local/src/dotfiles/apply-dotfiles
  322. log_changed "dotfiles were fetched and applied successfully"
  323. else
  324. log_ok "dotfiles were already fetched"
  325. fi
  326. }
  327. ensure_pkgs_installed "$BASE_PKGS" "some basic" "pacman"
  328. ensure_user_selected
  329. ensure_needed_dirs_created
  330. ensure_user_is_part_of_needed_groups
  331. ensure_sudo_is_symlinked_to_doas
  332. ensure_chaotic_aur_installed
  333. ensure_paru_installed
  334. ensure_pkgs_installed "$MAIN_PKGS" "main packages" "pacman"
  335. ensure_user_is_part_of_docker_group
  336. ensure_pkgs_installed "$AUR_PKGS" "AUR" "doas -u $username paru --mflags --ignorearch"
  337. ensure_dotfiles_are_fetched_and_applied
  338. ensure_global_zsh_installed
  339. ensure_history_file_exists
  340. ensure_login_shell_is_zsh
  341. setup_final_doas
  342. ensure_bluetooth_service_enabled
  343. ensure_docker_service_enabled
  344. ensure_dns_priority_in_nsswitch
  345. cleanup_home