diff --git a/README.md b/README.md index 075ab14..f02242a 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,17 @@ and nothing to re-apply after somebody else's release. ## Install +On a fresh Arch install, logged in as your own user with sudo and a network +connection: + +```bash +curl -fsSL https://git.sirblob.co/SirBlob/blomarchy/raw/branch/main/bootstrap.sh | bash +``` + +`bootstrap.sh` installs `git`, clones this repository to `~/dotfiles` (or +`BLOB_CHECKOUT_DIR`), and runs `install.sh`, passing any flags through with +`bash -s -- --autologin`. + `BLOB_PATH` is a symlink to this checkout, so `bin/`, `shell/`, `themes/` and `default/` are always the working tree and edits are live. Only the handful of files that must sit under `~/.config` get copied. @@ -75,6 +86,7 @@ It runs these steps, in this order, and every one of them is idempotent: | Step | What it does | | --- | --- | +| 0. Preflight | stops if run as root, off Arch, without working sudo, or offline, warns when another network manager is enabled next to NetworkManager, and keeps sudo alive for the whole run | | 1. Directories | creates the 19 directories the shell and scripts expect, before anything writes into them | | 2. `BLOB_PATH` | symlinks `~/.local/share/blob` to this checkout | | 3. Packages | installs what is missing from `packages/`, bootstrapping `yay` first on a machine that has no AUR helper | @@ -85,7 +97,10 @@ It runs these steps, in this order, and every one of them is idempotent: | 8. System services | enables NetworkManager, resolved, bluetooth, cups, avahi, docker, power-profiles-daemon, and oomd, masks `NetworkManager-wait-online`, sets the boot splash to `branding/blob_boot.png`, and sets GRUB up with every other installed OS in its menu | | 9. Login screen | installs the session entry and the SDDM config, enables `sddm.service`, and sets the default target to `graphical.target` | | 10. Reload | reloads systemd, restarts the running user services, and in a running session re-renders the theme, reloads Hyprland, and restarts the Blob shell | -| 11. Launch | starts the login screen, so the desktop comes up without a reboot | +| 11. Launch | offers a reboot, or starts the login screen straight away if you decline | + +Output is grouped into sections, and every warning is repeated in a summary at +the end so a failed AUR build is not lost in the scroll. Step 9 is the one a plain Arch install cannot do without: SDDM's stock Wayland greeter runs on `weston`, which Blob does not install, so the greeter is pointed diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..d7ee853 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +repo_url="${BLOB_REPO_URL:-https://git.sirblob.co/SirBlob/blomarchy.git}" +repo_branch="${BLOB_REPO_BRANCH:-main}" +checkout_dir="${BLOB_CHECKOUT_DIR:-$HOME/dotfiles}" + +if (( EUID == 0 )); then + echo "Run this as your own user, not as root. It asks for sudo when it needs it." >&2 + exit 1 +fi + +if ! command -v pacman &>/dev/null; then + echo "pacman not found; Blob installs on Arch Linux only." >&2 + exit 1 +fi + +if ! command -v git &>/dev/null; then + echo "Installing git" + sudo pacman -Syu --needed --noconfirm git +fi + +if [[ -d $checkout_dir/.git ]]; then + echo "Updating $checkout_dir" + git -C "$checkout_dir" pull --ff-only +else + echo "Cloning $repo_url into $checkout_dir" + git clone --branch "$repo_branch" "$repo_url" "$checkout_dir" +fi + +exec "$checkout_dir/install.sh" "$@" 0 )); do done source "$repo_dir/install/helpers/lib.sh" +source "$repo_dir/install/steps/preflight.sh" source "$repo_dir/install/steps/directories.sh" source "$repo_dir/install/steps/packages.sh" source "$repo_dir/install/steps/config.sh" @@ -60,12 +61,18 @@ say "repo: $repo_dir" if [[ $check == true ]]; then say "mode: check (nothing will be written)" fi -say "" +section "Preflight" +run_preflight + +section "Directories" create_directories link_blob_path + +section "Packages" install_packages +section "Config" install_shipped_config remove_omarchy_font refresh_font_cache @@ -76,16 +83,19 @@ install_systemd_dropins install_zen_config install_default_apps +section "Theme" link_wallpapers install_default_theme install_default_background link_app_themes +section "Services" enable_system_services mask_system_services install_boot_splash enable_user_units +section "Login screen" install_login_config install_session_entry install_autologin @@ -94,10 +104,13 @@ report_greeter_compositor report_login_conflicts report_desktop_essentials +section "Reload" reload_system_services reload_user_services reload_session +report_warnings + say "" if [[ $check == true ]]; then say "$changes item(s) would change." diff --git a/install/helpers/lib.sh b/install/helpers/lib.sh index f43d760..c9ab4ae 100644 --- a/install/helpers/lib.sh +++ b/install/helpers/lib.sh @@ -4,6 +4,28 @@ say() { printf '%s\n' "$1" } +warnings=() + +warn() { + warnings+=("$1") + say "WARN $1" +} + +section() { + say "" + say "==> $1" +} + +report_warnings() { + (( ${#warnings[@]} == 0 )) && return 0 + say "" + say "${#warnings[@]} warning(s), details above:" + local message + for message in "${warnings[@]}"; do + say " - $message" + done +} + note_change() { changes=$((changes + 1)) } diff --git a/install/steps/boot.sh b/install/steps/boot.sh index 2c4eead..8872549 100644 --- a/install/steps/boot.sh +++ b/install/steps/boot.sh @@ -32,6 +32,6 @@ install_boot_splash() { if BLOB_PATH="$blob_path" "$repo_dir/bin/blob-boot" "$boot_splash_image"; then say "set boot splash to branding/blob_boot.png" else - say "WARN could not set the boot splash; run 'blob boot' to retry" + warn "could not set the boot splash; run 'blob boot' to retry" fi } diff --git a/install/steps/config.sh b/install/steps/config.sh index 689c94b..f7cbbbe 100644 --- a/install/steps/config.sh +++ b/install/steps/config.sh @@ -15,7 +15,7 @@ link_blob_path() { fi if [[ -e $blob_path && ! -L $blob_path ]]; then - say "WARN $blob_path exists and is not a symlink; move it aside first" + warn "$blob_path exists and is not a symlink; move it aside first" note_change return 0 fi diff --git a/install/steps/login.sh b/install/steps/login.sh index b3d6106..c981b30 100644 --- a/install/steps/login.sh +++ b/install/steps/login.sh @@ -71,7 +71,7 @@ report_greeter_compositor() { [[ -n $binary ]] || return 0 command -v "$binary" &>/dev/null && return 0 - say "WARN the greeter's compositor command '$binary' is not installed" + warn "the greeter's compositor command '$binary' is not installed" say " SDDM cannot draw a greeter without it; it is set in $blob_greeter_conf" } @@ -93,7 +93,7 @@ report_login_conflicts() { session_entry_exists "$session" && continue found=true - say "WARN $file autologs into $session, which is not installed" + warn "$file autologs into $session, which is not installed" done if [[ $found == true ]]; then @@ -107,10 +107,10 @@ report_login_conflicts() { enable_display_manager() { if ! system_unit_exists sddm.service; then if package_installed sddm; then - say "WARN sddm is installed but sddm.service is not visible to systemd" + warn "sddm is installed but sddm.service is not visible to systemd" say " run 'sudo systemctl daemon-reload', then ./install.sh again" else - say "WARN sddm is not installed; the desktop cannot start at boot" + warn "sddm is not installed; the desktop cannot start at boot" say " install it with 'sudo pacman -S sddm', then ./install.sh again" fi return 0 @@ -144,7 +144,7 @@ enable_display_manager() { if as_root systemctl enable sddm.service; then say "enable sddm.service" else - say "WARN could not enable sddm.service" + warn "could not enable sddm.service" fi fi @@ -152,7 +152,7 @@ enable_display_manager() { if as_root systemctl set-default graphical.target; then say "set default target to graphical.target" else - say "WARN could not set the default target to graphical.target" + warn "could not set the default target to graphical.target" fi fi } @@ -184,8 +184,22 @@ launch_desktop() { return 0 fi + if reboot_chosen; then + say "Rebooting into Blob." + as_root systemctl reboot + return 0 + fi + say "Starting the login screen. Pick the Blob session to log in." as_root systemctl start sddm.service || { - say "WARN sddm.service did not start; see 'systemctl status sddm'" + warn "sddm.service did not start; see 'systemctl status sddm'" } } + +reboot_chosen() { + [[ -r /dev/tty ]] || return 1 + local answer + say "A reboot brings up the network, boot splash and new kernel modules together." + read -rp "Reboot now? [Y/n] " answer '" + warn "${#failed[@]} AUR package(s) failed to build: ${failed[*]}" + say " the install continued; retry them with 'yay -S '" } # A fresh Arch install has no AUR helper, and yay is not in the official @@ -148,7 +143,3 @@ ensure_aur_helper() { rm -rf "$build_dir" command -v yay &>/dev/null } - -blob_sudo_keepalive() { - sudo -v 2>/dev/null || true -} diff --git a/install/steps/preflight.sh b/install/steps/preflight.sh new file mode 100644 index 0000000..502a0ba --- /dev/null +++ b/install/steps/preflight.sh @@ -0,0 +1,85 @@ +conflicting_network_services=( + systemd-networkd.service + iwd.service + dhcpcd.service + netctl.service +) + +stop_install() { + printf 'ERROR %s\n' "$1" >&2 + shift + local detail + for detail in "$@"; do + printf ' %s\n' "$detail" >&2 + done + exit 1 +} + +require_regular_user() { + (( EUID != 0 )) && return 0 + stop_install "run install.sh as your own user, not as root" \ + "it installs into \$HOME and builds AUR packages, which makepkg refuses to do as root" \ + "it asks for sudo when it needs it" +} + +require_arch() { + command -v pacman &>/dev/null && return 0 + stop_install "pacman not found; Blob installs on Arch Linux only" +} + +require_sudo() { + [[ $check == true ]] && return 0 + command -v sudo &>/dev/null || stop_install "sudo is not installed" \ + "as root: pacman -S sudo, then add your user with visudo" + sudo -v || stop_install "sudo did not accept this user" \ + "as root: add your user to the wheel group and allow it in visudo" +} + +require_internet() { + [[ $check == true || $skip_packages == true ]] && return 0 + curl -fsS --max-time 15 -o /dev/null https://archlinux.org && return 0 + stop_install "no internet connection; packages cannot be downloaded" \ + "connect first (iwctl for Wi-Fi), or rerun with --skip-packages" +} + +report_network_conflicts() { + local unit + local -a enabled=() + + for unit in "${conflicting_network_services[@]}"; do + [[ "$(system_unit_state "$unit")" == enabled ]] && enabled+=("$unit") + done + + (( ${#enabled[@]} == 0 )) && return 0 + + warn "NetworkManager will be enabled, but so is: ${enabled[*]}" + say " two network managers fight over the same interfaces after a reboot" + say " once NetworkManager works: sudo systemctl disable ${enabled[*]}" +} + +sudo_keepalive_pid="" + +start_sudo_keepalive() { + [[ $check == true ]] && return 0 + while true; do + sudo -n true 2>/dev/null + sleep 60 + done & + sudo_keepalive_pid=$! + trap stop_sudo_keepalive EXIT +} + +stop_sudo_keepalive() { + [[ -n $sudo_keepalive_pid ]] && kill "$sudo_keepalive_pid" 2>/dev/null + sudo_keepalive_pid="" +} + +run_preflight() { + require_regular_user + require_arch + require_sudo + require_internet + report_network_conflicts + start_sudo_keepalive + say "ok preflight" +} diff --git a/install/steps/reload.sh b/install/steps/reload.sh index abcd4f7..3773103 100644 --- a/install/steps/reload.sh +++ b/install/steps/reload.sh @@ -13,14 +13,14 @@ reload_system_services() { fi if ! as_root systemctl daemon-reload; then - say "WARN could not reload systemd" + warn "could not reload systemd" return 0 fi local unit for unit in "${reloaded_system_services[@]}"; do system_unit_exists "$unit" || continue - as_root systemctl try-restart "$unit" || say "WARN could not restart $unit" + as_root systemctl try-restart "$unit" || warn "could not restart $unit" done say "ok system services reloaded" @@ -45,7 +45,7 @@ reload_user_services() { done if (( ${#failed[@]} > 0 )); then - say "WARN could not restart: ${failed[*]}" + warn "could not restart: ${failed[*]}" else say "ok user services reloaded" fi @@ -58,7 +58,7 @@ run_session_reload() { if BLOB_PATH="$blob_path" PATH="$blob_path/bin:$PATH" "$@" &>/dev/null; then say "ok $label reloaded" else - say "WARN could not reload $label" + warn "could not reload $label" fi } diff --git a/install/steps/services.sh b/install/steps/services.sh index 2820091..aa4c9ba 100644 --- a/install/steps/services.sh +++ b/install/steps/services.sh @@ -135,7 +135,7 @@ enable_user_units() { local unit if ! user_manager_available; then - say "WARN no user systemd manager; skipping user services" + warn "no user systemd manager; skipping user services" return 0 fi diff --git a/install/steps/theme.sh b/install/steps/theme.sh index e2eabb5..87c4fdb 100644 --- a/install/steps/theme.sh +++ b/install/steps/theme.sh @@ -55,7 +55,7 @@ install_default_theme() { blob-theme-set "$default_theme" >/dev/null; then say "theme $default_theme" else - say "WARN could not apply the $default_theme theme" + warn "could not apply the $default_theme theme" say " the session will come up without a palette; run 'blob theme set $default_theme'" fi }