Start the desktop after install by enabling the login screen and services
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# BLOB_PATH is a symlink to this checkout rather than a copy, so bin/, shell/,
|
||||
# themes/ and default/ are always the working tree. Every path the shell
|
||||
# resolves as $BLOB_PATH/... therefore needs no install step at all.
|
||||
link_blob_path() {
|
||||
local current=""
|
||||
[[ -L $blob_path ]] && current="$(readlink -f "$blob_path")"
|
||||
|
||||
if [[ $current == "$repo_dir" ]]; then
|
||||
say "ok BLOB_PATH -> $repo_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -e $blob_path && ! -L $blob_path ]]; then
|
||||
say "WARN $blob_path exists and is not a symlink; move it aside first"
|
||||
note_change
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
say "would link BLOB_PATH -> $repo_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
ln -sfn "$repo_dir" "$blob_path"
|
||||
say "link BLOB_PATH -> $repo_dir"
|
||||
}
|
||||
|
||||
# config/ is the shipped-defaults directory. Everything in it is reachable as
|
||||
# $BLOB_PATH/config/... so `blob-refresh-config` can reset a file, and most of it
|
||||
# is also the right thing to deploy on a fresh install. Two subdirectories are
|
||||
# reference-only and must not be copied into ~/.config:
|
||||
#
|
||||
# blob/ the shell defaults; copying them over ~/.config/blob/shell.json would
|
||||
# replace the user's bar layout with the stock one
|
||||
# hypr/ the stock Hyprland config; the personal hypr/ below is authoritative
|
||||
# and is installed to the same place
|
||||
install_shipped_config() {
|
||||
local entry entry_name
|
||||
|
||||
install_tree "$repo_dir/hypr" "$config_dir/hypr" "hypr"
|
||||
|
||||
for entry in "$repo_dir"/config/*; do
|
||||
entry_name="$(basename "$entry")"
|
||||
[[ $entry_name == blob || $entry_name == hypr ]] && continue
|
||||
if [[ -d $entry ]]; then
|
||||
install_tree "$entry" "$config_dir/$entry_name" "config/$entry_name"
|
||||
else
|
||||
install_file "$entry" "$config_dir/$entry_name" "config/$entry_name"
|
||||
fi
|
||||
done
|
||||
|
||||
install_tree "$repo_dir/hooks" "$config_dir/blob/hooks" "hooks"
|
||||
install_tree "$repo_dir/branding" "$config_dir/blob/branding" "branding"
|
||||
install_file "$repo_dir/shell.json" "$config_dir/blob/shell.json" "shell.json"
|
||||
install_file "$repo_dir/session/uwsm/default" "$config_dir/uwsm/default" "uwsm/default"
|
||||
install_file "$repo_dir/session/uwsm/env.d/10-blob" "$config_dir/uwsm/env.d/10-blob" "uwsm/env.d/10-blob"
|
||||
install_file "$repo_dir/fonts/omarchy.ttf" "$font_dir/omarchy.ttf" "fonts/omarchy.ttf"
|
||||
}
|
||||
|
||||
install_zen_config() {
|
||||
local profile
|
||||
profile="$(find "$config_dir/zen" -maxdepth 1 -type d -name '*.Default (release)*' 2>/dev/null | head -1)"
|
||||
|
||||
if [[ -z $profile ]]; then
|
||||
say "SKIP zen/userChrome.css (no Zen profile found)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
install_file "$repo_dir/zen/userChrome.css" "$profile/chrome/userChrome.css" "zen/userChrome.css"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
directories=(
|
||||
"$HOME/.config/blob/hooks"
|
||||
"$HOME/.config/blob/extensions"
|
||||
"$HOME/.config/blob/plugins"
|
||||
"$HOME/.config/blob/themed"
|
||||
"$HOME/.config/blob/themes"
|
||||
"$HOME/.config/blob/branding"
|
||||
"$HOME/.config/hypr"
|
||||
"$HOME/.config/uwsm/env.d"
|
||||
"$HOME/.config/systemd/user/app.slice.d"
|
||||
"$HOME/.local/state/blob/toggles/hypr"
|
||||
"$HOME/.local/state/blob/indicators"
|
||||
"$HOME/.local/state/blob/notifications/history"
|
||||
"$HOME/.local/state/blob/notifications/images"
|
||||
"$HOME/.local/state/blob/backgrounds"
|
||||
"$HOME/.local/state/blob/current"
|
||||
"$HOME/.local/share/fonts"
|
||||
"$HOME/.local/share/applications"
|
||||
"$HOME/.local/share/icons/hicolor/256x256/apps"
|
||||
"$HOME/wallpapers"
|
||||
)
|
||||
|
||||
create_directories() {
|
||||
local missing=()
|
||||
local dir
|
||||
for dir in "${directories[@]}"; do
|
||||
[[ -d $dir ]] || missing+=("$dir")
|
||||
done
|
||||
|
||||
if (( ${#missing[@]} == 0 )); then
|
||||
say "ok directories"
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
say "would create ${#missing[@]} directory(ies):"
|
||||
printf ' %s\n' "${missing[@]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "${missing[@]}"
|
||||
say "mkdir ${#missing[@]} directory(ies)"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
session_entry_dir=/usr/local/share/wayland-sessions
|
||||
greeter_compositor_config=/etc/sddm/hyprland-greeter.lua
|
||||
|
||||
# SDDM's stock Wayland greeter runs on weston, which Blob does not install.
|
||||
# Point it at Hyprland with a greeter-only config instead.
|
||||
install_login_config() {
|
||||
install_root_file "$repo_dir/default/sddm/hyprland-greeter.lua" \
|
||||
"$greeter_compositor_config" "sddm/hyprland-greeter.lua"
|
||||
install_root_file "$repo_dir/default/sddm/10-blob.conf" \
|
||||
/etc/sddm.conf.d/10-blob.conf "sddm.conf.d/10-blob.conf"
|
||||
}
|
||||
|
||||
install_session_entry() {
|
||||
install_root_file "$repo_dir/session/blob.desktop" \
|
||||
"$session_entry_dir/blob.desktop" "wayland session entry"
|
||||
}
|
||||
|
||||
install_autologin() {
|
||||
if [[ $autologin == false ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local generated
|
||||
generated="$(mktemp)"
|
||||
printf '[Autologin]\nUser=%s\nSession=blob.desktop\n' "$USER" >"$generated"
|
||||
install_root_file "$generated" \
|
||||
/etc/sddm.conf.d/99-blob-autologin.conf "sddm.conf.d/99-blob-autologin.conf"
|
||||
rm -f "$generated"
|
||||
}
|
||||
|
||||
# Enabling sddm is not enough on a machine installed without a display manager:
|
||||
# its default target is multi-user.target, which never pulls in graphical.target.
|
||||
enable_display_manager() {
|
||||
if ! system_unit_exists sddm.service; then
|
||||
say "WARN sddm.service not found; the desktop cannot start at boot"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local enable_needed=false
|
||||
case "$(system_unit_state sddm.service)" in
|
||||
enabled | enabled-runtime) ;;
|
||||
*) enable_needed=true ;;
|
||||
esac
|
||||
|
||||
local default_target
|
||||
default_target="$(systemctl get-default 2>/dev/null || true)"
|
||||
|
||||
local target_needed=false
|
||||
[[ $default_target == graphical.target ]] || target_needed=true
|
||||
|
||||
if [[ $enable_needed == false && $target_needed == false ]]; then
|
||||
say "ok display manager"
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
[[ $enable_needed == true ]] && say "would enable sddm.service"
|
||||
[[ $target_needed == true ]] && say "would set the default target to graphical.target"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $enable_needed == true ]]; then
|
||||
as_root systemctl enable sddm.service
|
||||
say "enable sddm.service"
|
||||
fi
|
||||
|
||||
if [[ $target_needed == true ]]; then
|
||||
as_root systemctl set-default graphical.target
|
||||
say "set default target to graphical.target"
|
||||
fi
|
||||
}
|
||||
|
||||
# The installer is usually run from a bare console on a fresh machine, where
|
||||
# enabling the display manager alone leaves the user looking at a shell prompt.
|
||||
launch_desktop() {
|
||||
if [[ $check == true ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ $no_launch == true ]]; then
|
||||
say "Run 'sudo systemctl start sddm' or reboot to reach the desktop."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! system_unit_exists sddm.service; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n ${WAYLAND_DISPLAY:-} || -n ${DISPLAY:-} ]]; then
|
||||
say "Log out and pick the Blob session at the login screen."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet sddm.service; then
|
||||
say "The login screen is already running."
|
||||
return 0
|
||||
fi
|
||||
|
||||
say "Starting the login screen. Pick the Blob session to log in."
|
||||
as_root systemctl start sddm.service
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
read_package_list() {
|
||||
[[ -f $1 ]] || return 0
|
||||
grep -vE '^[[:space:]]*#|^[[:space:]]*$' "$1"
|
||||
}
|
||||
|
||||
# A list entry may be "wanted|already-fine", which asks for the first name but
|
||||
# treats either as satisfying the requirement. That is how the AUR replacement
|
||||
# for a package still shipped by another repository avoids a file conflict.
|
||||
missing_packages() {
|
||||
local entry wanted alternative
|
||||
for entry in $(read_package_list "$1"); do
|
||||
wanted=${entry%%|*}
|
||||
alternative=${entry#*|}
|
||||
pacman -Q "$wanted" &>/dev/null && continue
|
||||
[[ $alternative != "$entry" ]] && pacman -Q "$alternative" &>/dev/null && continue
|
||||
printf '%s\n' "$wanted"
|
||||
done
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
if [[ $skip_packages == true ]]; then
|
||||
say "skip packages (--skip-packages)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -a repo_missing aur_missing
|
||||
mapfile -t repo_missing < <(missing_packages "$repo_dir/packages/blob.packages")
|
||||
mapfile -t aur_missing < <(missing_packages "$repo_dir/packages/blob-aur.packages")
|
||||
|
||||
if (( ${#repo_missing[@]} == 0 && ${#aur_missing[@]} == 0 )); then
|
||||
say "ok packages"
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
(( ${#repo_missing[@]} )) && say "would install ${#repo_missing[@]} repo package(s): ${repo_missing[*]}"
|
||||
(( ${#aur_missing[@]} )) && say "would install ${#aur_missing[@]} AUR package(s): ${aur_missing[*]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
blob_sudo_keepalive
|
||||
|
||||
if (( ${#repo_missing[@]} )); then
|
||||
say "install ${#repo_missing[@]} repo package(s)"
|
||||
as_root pacman -S --needed --noconfirm "${repo_missing[@]}"
|
||||
fi
|
||||
|
||||
if (( ${#aur_missing[@]} )); then
|
||||
ensure_aur_helper || {
|
||||
say "WARN no AUR helper; skipping ${#aur_missing[@]} AUR package(s): ${aur_missing[*]}"
|
||||
return 0
|
||||
}
|
||||
say "install ${#aur_missing[@]} AUR package(s)"
|
||||
yay -S --needed --noconfirm "${aur_missing[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# A fresh Arch install has no AUR helper, and yay is not in the official
|
||||
# repositories, so it is built from the AUR once with makepkg. Everything after
|
||||
# this point can then use yay normally.
|
||||
ensure_aur_helper() {
|
||||
command -v yay &>/dev/null && return 0
|
||||
|
||||
say "bootstrap yay from the AUR"
|
||||
as_root pacman -S --needed --noconfirm base-devel git
|
||||
|
||||
local build_dir
|
||||
build_dir="$(mktemp -d)"
|
||||
if ! git clone --depth 1 https://aur.archlinux.org/yay-bin.git "$build_dir/yay-bin"; then
|
||||
rm -rf "$build_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
( cd "$build_dir/yay-bin" && makepkg -si --noconfirm ) || {
|
||||
rm -rf "$build_dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
rm -rf "$build_dir"
|
||||
command -v yay &>/dev/null
|
||||
}
|
||||
|
||||
blob_sudo_keepalive() {
|
||||
sudo -v 2>/dev/null || true
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
system_services=(
|
||||
NetworkManager.service
|
||||
systemd-resolved.service
|
||||
bluetooth.service
|
||||
cups.service
|
||||
avahi-daemon.service
|
||||
docker.socket
|
||||
power-profiles-daemon.service
|
||||
systemd-oomd.service
|
||||
)
|
||||
|
||||
# network-online.target must not hold up graphical.target waiting for DHCP or
|
||||
# Wi-Fi association. Nothing in the session blocks on the network.
|
||||
masked_services=(
|
||||
NetworkManager-wait-online.service
|
||||
)
|
||||
|
||||
# blob-speaker-tuning.service is deliberately absent: blob-audio-tuning installs
|
||||
# and enables it itself, only on machines with a tuning profile.
|
||||
user_services=(
|
||||
bt-agent.service
|
||||
blob-sleep-lock.service
|
||||
blob-fcitx5.service
|
||||
blob-crash-watch.service
|
||||
)
|
||||
|
||||
enable_system_services() {
|
||||
local pending=()
|
||||
local unit
|
||||
|
||||
for unit in "${system_services[@]}"; do
|
||||
system_unit_exists "$unit" || continue
|
||||
case "$(system_unit_state "$unit")" in
|
||||
enabled | enabled-runtime | static | alias | indirect | masked) continue ;;
|
||||
esac
|
||||
pending+=("$unit")
|
||||
done
|
||||
|
||||
if (( ${#pending[@]} == 0 )); then
|
||||
say "ok system services"
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
say "would enable ${#pending[@]} system service(s): ${pending[*]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
as_root systemctl enable "${pending[@]}"
|
||||
say "enable ${#pending[@]} system service(s)"
|
||||
}
|
||||
|
||||
mask_system_services() {
|
||||
local pending=()
|
||||
local unit
|
||||
|
||||
for unit in "${masked_services[@]}"; do
|
||||
system_unit_exists "$unit" || continue
|
||||
[[ "$(system_unit_state "$unit")" == masked ]] && continue
|
||||
pending+=("$unit")
|
||||
done
|
||||
|
||||
if (( ${#pending[@]} == 0 )); then
|
||||
say "ok masked services"
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
say "would mask ${#pending[@]} service(s): ${pending[*]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
as_root systemctl mask "${pending[@]}"
|
||||
say "mask ${#pending[@]} service(s)"
|
||||
}
|
||||
|
||||
install_systemd_dropins() {
|
||||
local systemd_dir="$repo_dir/default/systemd"
|
||||
|
||||
install_root_file "$systemd_dir/faster-shutdown.conf" \
|
||||
/etc/systemd/system.conf.d/10-blob.conf "systemd/system.conf.d/10-blob.conf"
|
||||
install_root_file "$systemd_dir/user@.service.d/faster-shutdown.conf" \
|
||||
/etc/systemd/system/user@.service.d/10-blob.conf "systemd/user@.service.d/10-blob.conf"
|
||||
install_root_file "$systemd_dir/oomd.conf.d/10-blob.conf" \
|
||||
/etc/systemd/oomd.conf.d/10-blob.conf "systemd/oomd.conf.d/10-blob.conf"
|
||||
install_root_file "$systemd_dir/system-sleep/keyboard-backlight" \
|
||||
/etc/systemd/system-sleep/keyboard-backlight "systemd/system-sleep/keyboard-backlight" 0755
|
||||
install_root_file "$systemd_dir/system-sleep/unmount-fuse" \
|
||||
/etc/systemd/system-sleep/unmount-fuse "systemd/system-sleep/unmount-fuse" 0755
|
||||
|
||||
if system_unit_exists plocate-updatedb.service; then
|
||||
install_root_file "$systemd_dir/system/plocate-updatedb.service.d/10-blob.conf" \
|
||||
/etc/systemd/system/plocate-updatedb.service.d/10-blob.conf \
|
||||
"systemd/plocate-updatedb.service.d/10-blob.conf"
|
||||
else
|
||||
say "SKIP systemd/plocate-updatedb.service.d/10-blob.conf (plocate not installed)"
|
||||
fi
|
||||
}
|
||||
|
||||
install_user_units() {
|
||||
local unit
|
||||
|
||||
for unit in "${user_services[@]}"; do
|
||||
install_file "$repo_dir/default/systemd/user/$unit" \
|
||||
"$config_dir/systemd/user/$unit" "systemd/user/$unit"
|
||||
done
|
||||
|
||||
install_file "$repo_dir/default/systemd/user/app.slice.d/10-oomd.conf" \
|
||||
"$config_dir/systemd/user/app.slice.d/10-oomd.conf" "systemd/user/app.slice.d/10-oomd.conf"
|
||||
}
|
||||
|
||||
# Enable only. The units are all WantedBy=graphical-session.target, so they come
|
||||
# up with the session rather than from a TTY where their conditions cannot pass.
|
||||
enable_user_units() {
|
||||
local pending=()
|
||||
local unit
|
||||
|
||||
if ! user_manager_available; then
|
||||
say "WARN no user systemd manager; skipping user services"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for unit in "${user_services[@]}"; do
|
||||
[[ "$(user_unit_state "$unit")" == enabled ]] && continue
|
||||
pending+=("$unit")
|
||||
done
|
||||
|
||||
if (( ${#pending[@]} == 0 )); then
|
||||
say "ok user services"
|
||||
return 0
|
||||
fi
|
||||
|
||||
note_change
|
||||
if [[ $check == true ]]; then
|
||||
say "would enable ${#pending[@]} user service(s): ${pending[*]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable "${pending[@]}"
|
||||
say "enable ${#pending[@]} user service(s)"
|
||||
}
|
||||
Reference in New Issue
Block a user