-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·338 lines (314 loc) · 16.6 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·338 lines (314 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#!/usr/bin/env bash
# world80 installer — lays the config symlinks, installs packages, wires the
# per-machine bits, and seeds a starter theme so first boot isn't blank.
#
# safe to re-run: it backs up anything it would overwrite and skips work that's
# already done. nothing here is destructive without asking.
#
# git clone https://github.com/AidanMercer/world80 ~/dotfiles
# ~/dotfiles/install.sh
#
# flags: --no-packages skip pacman/paru
# --no-theme skip seeding a starter theme
# --no-frostify skip the (optional) frostify app clone
# --no-apps skip the (optional) app suite clones (mica/vellum/pulse/beryl)
# --yes assume yes to every prompt (unattended)
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="$HOME/.config/world80-backup-$(date +%Y%m%d-%H%M%S)"
THEMES_REPO="AidanMercer/themes" # marketplace source (pinned)
STARTER_THEME="moon" # seeded so the desktop looks alive day one
DO_PACKAGES=1 DO_THEME=1 DO_FROSTIFY=1 DO_APPS=1 ASSUME_YES=0
for a in "$@"; do case "$a" in
--no-packages) DO_PACKAGES=0 ;; --no-theme) DO_THEME=0 ;;
--no-frostify) DO_FROSTIFY=0 ;; --no-apps) DO_APPS=0 ;;
--yes|-y) ASSUME_YES=1 ;;
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//;1d'; exit 0 ;;
*) echo "unknown flag: $a" >&2; exit 1 ;;
esac; done
c_ok=$'\e[32m'; c_hi=$'\e[36m'; c_warn=$'\e[33m'; c_dim=$'\e[90m'; c_off=$'\e[0m'
say() { printf "%s==>%s %s\n" "$c_hi" "$c_off" "$*"; }
ok() { printf " %s✓%s %s\n" "$c_ok" "$c_off" "$*"; }
warn() { printf " %s!%s %s\n" "$c_warn" "$c_off" "$*"; }
skip() { printf " %s·%s %s\n" "$c_dim" "$c_off" "$*"; }
ask() { # ask "question" -> returns 0 for yes
[ "$ASSUME_YES" = 1 ] && return 0
read -rp " $1 [y/N] " r; [[ "$r" == [yY]* ]]
}
choose() { # choose "prompt" "opt1" "opt2"... -> sets REPLY to the 1-based pick
local prompt="$1"; shift
printf " %s\n" "$prompt"
local i=1; for o in "$@"; do printf " %s%d)%s %s\n" "$c_hi" "$i" "$c_off" "$o"; i=$((i+1)); done
if [ "$ASSUME_YES" = 1 ]; then REPLY=1; printf " -> 1 (--yes)\n"; return; fi
read -rp " choice [1]: " REPLY; REPLY="${REPLY:-1}"
}
cat <<EOF
${c_hi}world80 installer${c_off}
Sets up: packages · config symlinks · per-machine files · themes · Frostify · the app suite.
${c_dim}Backs up anything it replaces and asks before each big step. Ctrl-C anytime.${c_off}
EOF
# ── preflight ────────────────────────────────────────────────────────────
say "Preflight"
[ "$(id -u)" -ne 0 ] || { echo "don't run this as root — it installs into your home." >&2; exit 1; }
command -v pacman >/dev/null || { echo "this rice targets Arch (needs pacman)." >&2; exit 1; }
command -v git >/dev/null || { echo "git is required. sudo pacman -S git" >&2; exit 1; }
ok "Arch + git present, running as $USER"
# ── the engine hardcodes ~/dotfiles; make that path resolve wherever we cloned ──
# a dozen call sites reference ~/dotfiles by absolute path — hyprland's exec-once
# lines and script binds, lock.sh, the theme switcher, the boot splash, the engine
# self-update. qml can't resolve a path relative to the repo, so the clone has to
# be reachable at ~/dotfiles no matter where it actually lives.
say "Repo location"
# compare resolved paths: re-running from ~/world80 when ~/dotfiles already links
# here is the normal case on a second run, not a conflict
if [ "$(readlink -f "$HOME/dotfiles" 2>/dev/null)" = "$(readlink -f "$REPO_DIR")" ]; then
ok "~/dotfiles resolves to this repo"
elif [ ! -e "$HOME/dotfiles" ]; then
ln -s "$REPO_DIR" "$HOME/dotfiles"
ok "symlinked ~/dotfiles -> $REPO_DIR (configs reference ~/dotfiles by path)"
else
# this used to warn and carry on, which was the worst outcome: the symlinks all
# land, the shell even starts, and then every script bind and the whole theme
# pipeline silently points into someone else's tree. stop instead.
warn "~/dotfiles already exists and isn't this repo."
warn "the configs hardcode that path, so the rice stays half-wired until it resolves here."
if ask "back up ~/dotfiles into $BACKUP_DIR and point it at this clone?"; then
mkdir -p "$BACKUP_DIR"; mv "$HOME/dotfiles" "$BACKUP_DIR/dotfiles"
ln -s "$REPO_DIR" "$HOME/dotfiles"
ok "old ~/dotfiles -> $BACKUP_DIR/dotfiles · ~/dotfiles -> $REPO_DIR"
else
warn "nothing changed. move this clone to ~/dotfiles or free that path, then re-run."
exit 1
fi
fi
# ── packages ─────────────────────────────────────────────────────────────
pkgs_in_section() { # $1 = section tag like [repo]
sed -n "/^\[$1\]/,/^\[/p" "$REPO_DIR/packages.txt" | grep -vE '^\s*#|^\s*\[|^\s*$'
}
if [ "$DO_PACKAGES" = 1 ]; then
say "Packages (needs sudo)"
mapfile -t repo_pkgs < <(pkgs_in_section repo)
mapfile -t aur_pkgs < <(pkgs_in_section aur)
if [ "${#repo_pkgs[@]}" -gt 0 ]; then
say " repo: ${repo_pkgs[*]}"
ask "install these with pacman?" && sudo pacman -S --needed "${repo_pkgs[@]}" || skip "skipped repo packages"
fi
if ! command -v paru >/dev/null && [ "${#aur_pkgs[@]}" -gt 0 ]; then
if ask "paru (AUR helper) isn't installed — bootstrap it?"; then
tmp="$(mktemp -d)"; git clone --depth 1 https://aur.archlinux.org/paru.git "$tmp/paru"
( cd "$tmp/paru" && makepkg -si --noconfirm ); rm -rf "$tmp"
ok "paru installed"
fi
fi
if command -v paru >/dev/null && [ "${#aur_pkgs[@]}" -gt 0 ]; then
say " aur: ${aur_pkgs[*]}"
ask "install these with paru?" && paru -S --needed "${aur_pkgs[@]}" || skip "skipped aur packages"
fi
else
skip "package install skipped (--no-packages)"
fi
# ── config symlinks (mirror the by-hand layout; back up anything real) ────
say "Config symlinks"
# tracked .config entries when it's a git checkout; fall back to a plain listing
# for a tarball download (skip the untracked per-machine hypr files handled below)
if git -C "$REPO_DIR" rev-parse --git-dir >/dev/null 2>&1; then
mapfile -t entries < <(git -C "$REPO_DIR" ls-files .config | cut -d/ -f2 | sort -u)
else
mapfile -t entries < <(cd "$REPO_DIR/.config" && ls -1)
fi
for name in "${entries[@]}"; do
if [ "$name" = qt6ct ]; then continue; fi # copied, not linked — see below
src="$REPO_DIR/.config/$name"; dst="$HOME/.config/$name"
if [ -L "$dst" ] && [ "$(readlink -f "$dst")" = "$(readlink -f "$src")" ]; then
skip "$name (already linked)"; continue
fi
if [ -e "$dst" ] || [ -L "$dst" ]; then
mkdir -p "$BACKUP_DIR"; mv "$dst" "$BACKUP_DIR/$name"
warn "$name existed — backed up to $BACKUP_DIR/$name"
fi
mkdir -p "$(dirname "$dst")"; ln -s "$src" "$dst"; ok "$name -> repo"
done
# qt6ct is the one config we copy instead of link: it stores an absolute
# color-scheme path (no ~ support) and qt6ct rewrites the file itself, so a
# symlink would leave every clone permanently dirty and break the shell's
# ff-only self-update pull.
qtsrc="$REPO_DIR/.config/qt6ct"; qtdst="$HOME/.config/qt6ct"
if [ -L "$qtdst" ]; then
mkdir -p "$BACKUP_DIR"; mv "$qtdst" "$BACKUP_DIR/qt6ct"
warn "qt6ct was linked into the repo — backed up, using a real copy now"
fi
if [ ! -e "$qtdst/qt6ct.conf" ]; then
mkdir -p "$qtdst"; cp -r "$qtsrc/." "$qtdst/"
sed -i "s#/home/aidan#$HOME#g" "$qtdst/qt6ct.conf"
ok "qt6ct copied to ~/.config/qt6ct (color path -> $HOME)"
else
skip "qt6ct exists (left yours alone)"
fi
# ── per-machine hypr conf (untracked; hyprland.conf sources these) ────────
say "Per-machine config"
mon="$REPO_DIR/.config/hypr/monitors.conf"
loc="$REPO_DIR/.config/hypr/local.conf"
if [ ! -e "$mon" ]; then
printf '# per-machine monitor layout. see `hyprctl monitors`.\nmonitor = , preferred, auto, 1\n' > "$mon"
ok "wrote a default monitors.conf (edit for your displays)"
else skip "monitors.conf exists"; fi
# hyprland.conf hardcodes LIBVA_DRIVER_NAME=nvidia (author's desktop). local.conf
# is sourced AFTER it and wins, so we detect the GPU and set the right VAAPI
# driver here — otherwise AMD/Intel video wallpapers fall back to CPU decode.
# This only writes a userspace env var; it does NOT install or touch drivers.
gpu_vendor() {
local g; g="$(lspci 2>/dev/null | grep -iE 'vga|3d|display' || true)"
echo "$g" | grep -qi nvidia && { echo nvidia; return; }
echo "$g" | grep -qiE 'amd|ati|radeon' && { echo amd; return; }
echo "$g" | grep -qi intel && { echo intel; return; }
echo unknown
}
if [ ! -e "$loc" ]; then
v="$(gpu_vendor)"
case "$v" in
amd) libva="radeonsi" ;;
intel) libva="iHD" ;;
nvidia) libva="nvidia" ;;
*) libva="" ;;
esac
{
printf '# per-machine env/overrides, sourced by hyprland.conf (last wins).\n'
if [ -n "$libva" ]; then
printf '# detected a %s GPU — VAAPI driver for hardware video decode:\n' "$v"
printf 'env = LIBVA_DRIVER_NAME,%s\n' "$libva"
else
printf '# couldn'"'"'t detect the GPU — set your VAAPI driver by hand:\n'
printf '# AMD: env = LIBVA_DRIVER_NAME,radeonsi\n'
printf '# Intel:env = LIBVA_DRIVER_NAME,iHD\n'
printf '# (nvidia is already set in hyprland.conf)\n'
fi
} > "$loc"
ok "wrote local.conf (GPU: $v${libva:+, LIBVA_DRIVER_NAME=$libva})"
case "$v" in
nvidia) warn "nvidia: make sure nvidia-open-dkms + nvidia-utils are installed (base system)" ;;
amd) warn "amd: make sure mesa + vulkan-radeon + libva-mesa-driver are installed (base system)" ;;
intel) warn "intel: make sure mesa + vulkan-intel + intel-media-driver are installed (base system)" ;;
esac
else skip "local.conf exists (left your GPU env alone)"; fi
# ── themes: a plain dir the marketplace fills, plus an optional first batch ─
say "Themes"
mkdir -p "$HOME/.config/themes"; ok "~/.config/themes ready"
# the shell layers every theme over themes/default/config.toml, so that base
# always has to exist. it isn't a marketplace theme (no wallpaper) — just the
# palette floor — so seed its couple of files directly.
if [ ! -f "$HOME/.config/themes/default/config.toml" ]; then
mkdir -p "$HOME/.config/themes/default"; base_ok=1
for f in config.toml frostify.qml mica.qml vellum.qml; do
curl -fsSL "https://raw.githubusercontent.com/$THEMES_REPO/master/default/$f" \
-o "$HOME/.config/themes/default/$f" 2>/dev/null || base_ok=0
done
[ "$base_ok" = 1 ] && ok "installed the base 'default' theme (palette floor)" \
|| warn "couldn't fetch the default base — check your connection and re-run"
else skip "default base theme present"; fi
THEME_IDX="" THEME_COMMIT=""
theme_catalog() { # fetch index.json once; needs jq + curl
[ -n "$THEME_IDX" ] && return 0
command -v jq >/dev/null || { command -v pacman >/dev/null && ask "themes need jq — install it?" && sudo pacman -S --needed jq; }
command -v jq >/dev/null || { warn "jq missing — skipping; use the Marketplace tab in-session instead"; return 1; }
THEME_IDX="$(curl -fsSL "https://raw.githubusercontent.com/$THEMES_REPO/master/index.json")" || { warn "couldn't reach the theme store"; return 1; }
THEME_COMMIT="$(jq -r '.commit' <<<"$THEME_IDX")"
}
install_theme() { # $1 = name
local name="$1" paths
[ -d "$HOME/.config/themes/$name" ] && { skip "$name (already installed)"; return 0; }
mapfile -t paths < <(jq -r ".themes[]|select(.name==\"$1\")|.files[].path" <<<"$THEME_IDX")
[ "${#paths[@]}" -gt 0 ] || { warn "no theme named '$name' in the catalog"; return 1; }
printf " downloading %s…\n" "$name"
bash "$REPO_DIR/.config/quickshell/scripts/theme-install.sh" \
"$THEMES_REPO" master "$name" "$THEME_COMMIT" "${paths[@]}" >/dev/null \
&& ok "$name installed" || warn "$name failed"
}
if [ "$DO_THEME" = 1 ]; then
choose "How many themes to download now? (the rest are one click away in Super+/ → Marketplace)" \
"just a starter ($STARTER_THEME, ~40MB)" \
"all themes (~240MB)" \
"let me pick" \
"none — I'll grab them from the Marketplace later"
case "$REPLY" in
1) theme_catalog && install_theme "$STARTER_THEME" ;;
2) if theme_catalog; then
for t in $(jq -r '.themes[].name' <<<"$THEME_IDX"); do install_theme "$t"; done
fi ;;
3) if theme_catalog; then
echo " available:"
jq -r '.themes[] | " \(.name) · \((.bytes/1048576)|floor)MB · \(.tagline)"' <<<"$THEME_IDX"
read -rp " names to install (space-separated): " picks
for t in $picks; do install_theme "$t"; done
fi ;;
*) skip "no themes seeded — open Super+/ → Marketplace anytime" ;;
esac
else
skip "themes skipped (--no-theme)"
fi
# ── login shell ───────────────────────────────────────────────────────────
# the tty1 autostart that execs Hyprland is a fish conf.d snippet, so a bash
# login shell means it never fires.
say "Login shell"
cur_shell="$(getent passwd "$USER" | cut -d: -f7)"
fish_bin="$(command -v fish || true)"
if [ -z "$fish_bin" ]; then
warn "fish isn't installed — the tty1 autostart won't run without it"
elif [ "$cur_shell" = "$fish_bin" ]; then
skip "login shell is already fish"
elif ask "make fish your login shell? (chsh — needed for the tty1 autostart)"; then
chsh -s "$fish_bin" && ok "login shell -> fish (next login)" || warn "chsh failed — run 'chsh -s $fish_bin' by hand"
else
skip "left as $cur_shell — you'll have to start Hyprland yourself"
fi
# ── services ──────────────────────────────────────────────────────────────
say "Services"
if ask "enable NetworkManager + bluetooth (system) and wireplumber (user)?"; then
sudo systemctl enable --now NetworkManager.service bluetooth.service || warn "system services: check manually"
systemctl --user enable --now wireplumber.service || warn "wireplumber: check manually"
ok "services enabled"
else skip "services left alone"; fi
# ── app suite (optional, separate repos behind the Super+E/N/B/S/Escape binds) ─
# each install goes through ext-install.sh — the same script the Super+/
# Extensions tab uses — so a CLI install gets the identical setup: clone,
# desktop entry pointing at the real path, mica's file-picker portal, dep check.
EXT_SH="$REPO_DIR/.config/quickshell/scripts/ext-install.sh"
ext_install() { # $1 = app name
local out deps
out="$(bash "$EXT_SH" install "$1" 2>/dev/null)" || true
if grep -q '^DONE' <<<"$out"; then ok "$1 installed (+ desktop entry)"
else warn "$1: $(grep '^ERR' <<<"$out" | head -1 | cut -d' ' -f2- || echo 'install failed')"; fi
deps="$(grep '^DEPS' <<<"$out" | cut -d' ' -f3-)"
[ -n "$deps" ] && warn "$1 still needs: $deps (install with pacman/paru, then re-run)"
}
if [ "$DO_FROSTIFY" = 1 ] && [ ! -d "$HOME/frostify" ]; then
say "Frostify (optional)"
echo " A full-screen now-playing overlay — album art, playlists, and playback"
echo " controls — that follows Spotify/MPRIS and matches your active theme (Super+S)."
ask "install Frostify to ~/frostify?" && ext_install frostify
fi
if [ "$DO_APPS" = 1 ]; then
say "App suite (optional)"
echo " Theme-following companions, each its own repo under ~/dev:"
echo " mica (files, Super+E) · vellum (editor, Super+N) ·"
echo " pulse (system monitor, Super+Escape) · beryl (browser, Super+B) ·"
echo " cobalt (teams, Super+K)"
for app in mica vellum pulse beryl cobalt; do
if [ -d "$HOME/dev/$app" ]; then skip "$app (already at ~/dev/$app)"; continue; fi
ask "install $app to ~/dev/$app?" && ext_install "$app"
done
else
skip "app suite skipped (--no-apps)"
fi
# ── done ──────────────────────────────────────────────────────────────────
say "Done"
cat <<EOF
Log out and start Hyprland — \`start-hyprland\` from a TTY. (There's no display
manager here: the rice boots to tty1 and the fish autostart launches it.)
${c_dim}Set your display layout in ~/.config/hypr/monitors.conf if the default
(auto, 1x scale) isn't right. The wallpaper restores to your last-applied
theme on login (or a starter one if you seeded any).${c_off}
Keys: Super+/ this cheatsheet (+ Marketplace & Extensions tabs) · Super+Shift+T themes ·
Super+R launcher · Super+M control center ·
Super+E files · Super+N editor · Super+B browser · Super+Escape monitor
$( [ -d "${BACKUP_DIR}" ] && printf "\n Backed-up originals are in %s\n" "$BACKUP_DIR" )
EOF