Streamline the installer for fresh Arch installs

This commit is contained in:
SirBlob committed 2026-09-24 12:00:22 -04:00
1 parent 501d066e64
commit d59725f193
14 files changed
+234 -44

No files matched your search

+16 -1
View File
@@ -59,6 +59,17 @@ and nothing to re-apply after somebody else's release.
## Install ## 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 `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 `default/` are always the working tree and edits are live. Only the handful of
files that must sit under `~/.config` get copied. 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 | | 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 | | 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 | | 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 | | 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 | | 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` | | 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 | | 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 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 greeter runs on `weston`, which Blob does not install, so the greeter is pointed
Executable
+32
View File
@@ -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" "$@" </dev/tty
+22 -6
View File
@@ -8,8 +8,10 @@ with `--check`.
| Path | Role | | Path | Role |
| --- | --- | | --- | --- |
| `bootstrap.sh` | one-command install on a bare machine: installs git, clones, runs `install.sh` |
| `install.sh` | flags and the order the steps run in | | `install.sh` | flags and the order the steps run in |
| `install/helpers/lib.sh` | reporting, file copies, unit queries | | `install/helpers/lib.sh` | reporting, sections, the warning summary, file copies, unit queries |
| `install/steps/preflight.sh` | the checks that stop a run early, and the sudo keepalive |
| `install/helpers/as-root.sh` | `as_root`, a no-op when already root | | `install/helpers/as-root.sh` | `as_root`, a no-op when already root |
| `install/steps/directories.sh` | the directories the shell and scripts expect | | `install/steps/directories.sh` | the directories the shell and scripts expect |
| `install/steps/packages.sh` | repo and AUR packages, and the yay bootstrap | | `install/steps/packages.sh` | repo and AUR packages, and the yay bootstrap |
@@ -23,6 +25,16 @@ with `--check`.
## Steps ## Steps
0. **Preflight** stops before anything is written when the installer is run as
root (it installs into `$HOME`, and `makepkg` refuses to build as root), when
`pacman` is missing, when `sudo` does not accept the user, or when
`archlinux.org` cannot be reached and packages are wanted. It warns when
`systemd-networkd`, `iwd`, `dhcpcd` or `netctl` is enabled, because
NetworkManager is enabled below and two network managers fight over the same
interface after a reboot. `archinstall`'s "copy ISO network config" option
leaves exactly that behind. Once sudo is accepted it is refreshed every
minute in the background, so a long AUR build does not stop halfway for a
password.
1. **Directories** are created first, because nothing below can write into a 1. **Directories** are created first, because nothing below can write into a
directory that is not there: `~/.config/blob`, `~/.config/systemd/user/app.slice.d`, directory that is not there: `~/.config/blob`, `~/.config/systemd/user/app.slice.d`,
`~/.local/state/blob`, `~/.local/share` and `~/.cache/blob`. A fresh Arch `~/.local/state/blob`, `~/.local/share` and `~/.cache/blob`. A fresh Arch
@@ -32,9 +44,10 @@ with `--check`.
create the parent of a link, so this step makes `~/.local/share` itself rather create the parent of a link, so this step makes `~/.local/share` itself rather
than assuming step 1 ran. than assuming step 1 ran.
3. **Packages** come from `packages/blob.packages` and 3. **Packages** come from `packages/blob.packages` and
`packages/blob-aur.packages`. A machine with no AUR helper gets `yay-bin` `packages/blob-aur.packages`. Repo packages go in with a full
built once with `makepkg`. A failed batch refreshes the databases and retries, `pacman -Syu`, never a partial upgrade. A machine with no AUR helper gets
then falls back to one package at a time, so a single name that no longer `yay-bin` built once with `makepkg`. A failed batch upgrades the system on
its own, then falls back to one package at a time, so a single name that no longer
resolves cannot cost you the other hundred and the config, service and login resolves cannot cost you the other hundred and the config, service and login
steps below. See [packages.md](packages.md). steps below. See [packages.md](packages.md).
4. **Config** is copied into `~/.config`: `hypr/`, the themed app configs, 4. **Config** is copied into `~/.config`: `hypr/`, the themed app configs,
@@ -152,8 +165,11 @@ with `--check`.
11. **Launch.** `sddm`, `hyprland`, `quickshell` and `uwsm` are checked one last 11. **Launch.** `sddm`, `hyprland`, `quickshell` and `uwsm` are checked one last
time and any that are missing are named, because that is the whole difference time and any that are missing are named, because that is the whole difference
between a desktop and a console. Then, when the installer was run from a between a desktop and a console. Then, when the installer was run from a
console and nothing graphical is running, it starts `sddm.service` so the console and nothing graphical is running, it offers a reboot, which brings
desktop appears without a reboot. `--no-launch` prints the command instead. up NetworkManager, the boot splash and any new kernel modules together.
Declining starts `sddm.service` so the desktop appears without one.
`--no-launch` prints the command instead. Every warning from the run is
listed again just before this step.
`--autologin` writes `/etc/sddm.conf.d/zzz-blob-autologin.conf` for the `--autologin` writes `/etc/sddm.conf.d/zzz-blob-autologin.conf` for the
current user. current user.
+6 -4
View File
@@ -84,10 +84,12 @@ packages pulled in under a different name no longer come back on every run. AUR
packages are built one at a time, so a build that fails is reported at the end packages are built one at a time, so a build that fails is reported at the end
instead of stopping the install. instead of stopping the install.
Repo packages go in one `pacman` call. If that call fails the databases are Repo packages go in one `pacman -Syu --needed` call, so the databases are
refreshed with `pacman -Sy` and it is tried again, because a stale database is fresh and the system is upgraded in the same transaction. Refreshing the
what turns an installed-and-present package into `target not found`. If it still databases without upgrading (`-Sy` then `-S`) is a partial upgrade, which Arch
fails the packages are installed one at a time, so one unresolvable name does not does not support and which breaks libraries on a fresh install whose ISO is a
few weeks old. If that call fails the system is upgraded on its own and the
packages are installed one at a time, so one unresolvable name does not
block the other hundred - which is how a missing `sddm` used to turn into a block the other hundred - which is how a missing `sddm` used to turn into a
machine with no login screen. `install.sh` ends by naming any of `sddm`, machine with no login screen. `install.sh` ends by naming any of `sddm`,
`hyprland`, `quickshell` and `uwsm` that are still not installed. `hyprland`, `quickshell` and `uwsm` that are still not installed.
+14 -1
View File
@@ -45,6 +45,7 @@ while (( $# > 0 )); do
done done
source "$repo_dir/install/helpers/lib.sh" 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/directories.sh"
source "$repo_dir/install/steps/packages.sh" source "$repo_dir/install/steps/packages.sh"
source "$repo_dir/install/steps/config.sh" source "$repo_dir/install/steps/config.sh"
@@ -60,12 +61,18 @@ say "repo: $repo_dir"
if [[ $check == true ]]; then if [[ $check == true ]]; then
say "mode: check (nothing will be written)" say "mode: check (nothing will be written)"
fi fi
say ""
section "Preflight"
run_preflight
section "Directories"
create_directories create_directories
link_blob_path link_blob_path
section "Packages"
install_packages install_packages
section "Config"
install_shipped_config install_shipped_config
remove_omarchy_font remove_omarchy_font
refresh_font_cache refresh_font_cache
@@ -76,16 +83,19 @@ install_systemd_dropins
install_zen_config install_zen_config
install_default_apps install_default_apps
section "Theme"
link_wallpapers link_wallpapers
install_default_theme install_default_theme
install_default_background install_default_background
link_app_themes link_app_themes
section "Services"
enable_system_services enable_system_services
mask_system_services mask_system_services
install_boot_splash install_boot_splash
enable_user_units enable_user_units
section "Login screen"
install_login_config install_login_config
install_session_entry install_session_entry
install_autologin install_autologin
@@ -94,10 +104,13 @@ report_greeter_compositor
report_login_conflicts report_login_conflicts
report_desktop_essentials report_desktop_essentials
section "Reload"
reload_system_services reload_system_services
reload_user_services reload_user_services
reload_session reload_session
report_warnings
say "" say ""
if [[ $check == true ]]; then if [[ $check == true ]]; then
say "$changes item(s) would change." say "$changes item(s) would change."
+22
View File
@@ -4,6 +4,28 @@ say() {
printf '%s\n' "$1" 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() { note_change() {
changes=$((changes + 1)) changes=$((changes + 1))
} }
+1 -1
View File
@@ -32,6 +32,6 @@ install_boot_splash() {
if BLOB_PATH="$blob_path" "$repo_dir/bin/blob-boot" "$boot_splash_image"; then if BLOB_PATH="$blob_path" "$repo_dir/bin/blob-boot" "$boot_splash_image"; then
say "set boot splash to branding/blob_boot.png" say "set boot splash to branding/blob_boot.png"
else 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 fi
} }
+1 -1
View File
@@ -15,7 +15,7 @@ link_blob_path() {
fi fi
if [[ -e $blob_path && ! -L $blob_path ]]; then 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 note_change
return 0 return 0
fi fi
+21 -7
View File
@@ -71,7 +71,7 @@ report_greeter_compositor() {
[[ -n $binary ]] || return 0 [[ -n $binary ]] || return 0
command -v "$binary" &>/dev/null && 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" 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 session_entry_exists "$session" && continue
found=true found=true
say "WARN $file autologs into $session, which is not installed" warn "$file autologs into $session, which is not installed"
done done
if [[ $found == true ]]; then if [[ $found == true ]]; then
@@ -107,10 +107,10 @@ report_login_conflicts() {
enable_display_manager() { enable_display_manager() {
if ! system_unit_exists sddm.service; then if ! system_unit_exists sddm.service; then
if package_installed sddm; 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" say " run 'sudo systemctl daemon-reload', then ./install.sh again"
else 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" say " install it with 'sudo pacman -S sddm', then ./install.sh again"
fi fi
return 0 return 0
@@ -144,7 +144,7 @@ enable_display_manager() {
if as_root systemctl enable sddm.service; then if as_root systemctl enable sddm.service; then
say "enable sddm.service" say "enable sddm.service"
else else
say "WARN could not enable sddm.service" warn "could not enable sddm.service"
fi fi
fi fi
@@ -152,7 +152,7 @@ enable_display_manager() {
if as_root systemctl set-default graphical.target; then if as_root systemctl set-default graphical.target; then
say "set default target to graphical.target" say "set default target to graphical.target"
else else
say "WARN could not set the default target to graphical.target" warn "could not set the default target to graphical.target"
fi fi
fi fi
} }
@@ -184,8 +184,22 @@ launch_desktop() {
return 0 return 0
fi 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." say "Starting the login screen. Pick the Blob session to log in."
as_root systemctl start sddm.service || { 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 </dev/tty
[[ -z $answer || $answer == [yY]* ]]
}
+8 -17
View File
@@ -54,8 +54,6 @@ install_packages() {
return 0 return 0
fi fi
blob_sudo_keepalive
if (( ${#repo_missing[@]} )); then if (( ${#repo_missing[@]} )); then
say "install ${#repo_missing[@]} repo package(s)" say "install ${#repo_missing[@]} repo package(s)"
install_repo_packages "${repo_missing[@]}" install_repo_packages "${repo_missing[@]}"
@@ -63,7 +61,7 @@ install_packages() {
if (( ${#aur_missing[@]} )); then if (( ${#aur_missing[@]} )); then
ensure_aur_helper || { ensure_aur_helper || {
say "WARN no AUR helper; skipping ${#aur_missing[@]} AUR package(s): ${aur_missing[*]}" warn "no AUR helper; skipping ${#aur_missing[@]} AUR package(s): ${aur_missing[*]}"
return 0 return 0
} }
say "install ${#aur_missing[@]} AUR package(s)" say "install ${#aur_missing[@]} AUR package(s)"
@@ -75,20 +73,17 @@ install_repo_packages() {
local package local package
local -a failed=() local -a failed=()
as_root pacman -S --needed --noconfirm "$@" && return 0 as_root pacman -Syu --needed --noconfirm "$@" && return 0
say "WARN the batch install failed; refreshing the package databases" say " the batch install failed; upgrading the system, then one package at a time"
as_root pacman -Sy --noconfirm || true as_root pacman -Syu --noconfirm || true
as_root pacman -S --needed --noconfirm "$@" && return 0
say "WARN still failing; retrying one package at a time"
for package in "$@"; do for package in "$@"; do
as_root pacman -S --needed --noconfirm "$package" || failed+=("$package") as_root pacman -S --needed --noconfirm "$package" || failed+=("$package")
done done
(( ${#failed[@]} == 0 )) && return 0 (( ${#failed[@]} == 0 )) && return 0
say "WARN ${#failed[@]} repo package(s) failed to install: ${failed[*]}" warn "${#failed[@]} repo package(s) failed to install: ${failed[*]}"
} }
desktop_essentials=(sddm hyprland quickshell uwsm) desktop_essentials=(sddm hyprland quickshell uwsm)
@@ -106,7 +101,7 @@ report_desktop_essentials() {
return 0 return 0
fi fi
say "WARN the desktop cannot start without: ${missing[*]}" warn "the desktop cannot start without: ${missing[*]}"
say " install them with 'sudo pacman -S ${missing[*]}', then ./install.sh again" say " install them with 'sudo pacman -S ${missing[*]}', then ./install.sh again"
} }
@@ -120,8 +115,8 @@ install_aur_packages() {
(( ${#failed[@]} == 0 )) && return 0 (( ${#failed[@]} == 0 )) && return 0
say "WARN ${#failed[@]} AUR package(s) failed to build: ${failed[*]}" warn "${#failed[@]} AUR package(s) failed to build: ${failed[*]}"
say "WARN the install continued; retry them with 'yay -S <name>'" say " the install continued; retry them with 'yay -S <name>'"
} }
# A fresh Arch install has no AUR helper, and yay is not in the official # 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" rm -rf "$build_dir"
command -v yay &>/dev/null command -v yay &>/dev/null
} }
blob_sudo_keepalive() {
sudo -v 2>/dev/null || true
}
+85
View File
@@ -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"
}
+4 -4
View File
@@ -13,14 +13,14 @@ reload_system_services() {
fi fi
if ! as_root systemctl daemon-reload; then if ! as_root systemctl daemon-reload; then
say "WARN could not reload systemd" warn "could not reload systemd"
return 0 return 0
fi fi
local unit local unit
for unit in "${reloaded_system_services[@]}"; do for unit in "${reloaded_system_services[@]}"; do
system_unit_exists "$unit" || continue 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 done
say "ok system services reloaded" say "ok system services reloaded"
@@ -45,7 +45,7 @@ reload_user_services() {
done done
if (( ${#failed[@]} > 0 )); then if (( ${#failed[@]} > 0 )); then
say "WARN could not restart: ${failed[*]}" warn "could not restart: ${failed[*]}"
else else
say "ok user services reloaded" say "ok user services reloaded"
fi fi
@@ -58,7 +58,7 @@ run_session_reload() {
if BLOB_PATH="$blob_path" PATH="$blob_path/bin:$PATH" "$@" &>/dev/null; then if BLOB_PATH="$blob_path" PATH="$blob_path/bin:$PATH" "$@" &>/dev/null; then
say "ok $label reloaded" say "ok $label reloaded"
else else
say "WARN could not reload $label" warn "could not reload $label"
fi fi
} }
+1 -1
View File
@@ -135,7 +135,7 @@ enable_user_units() {
local unit local unit
if ! user_manager_available; then if ! user_manager_available; then
say "WARN no user systemd manager; skipping user services" warn "no user systemd manager; skipping user services"
return 0 return 0
fi fi
+1 -1
View File
@@ -55,7 +55,7 @@ install_default_theme() {
blob-theme-set "$default_theme" >/dev/null; then blob-theme-set "$default_theme" >/dev/null; then
say "theme $default_theme" say "theme $default_theme"
else 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'" say " the session will come up without a palette; run 'blob theme set $default_theme'"
fi fi
} }