diff --git a/.gitea/workflows/build-iso.yaml b/.gitea/workflows/build-iso.yaml index ec56cb5..68fa23d 100644 --- a/.gitea/workflows/build-iso.yaml +++ b/.gitea/workflows/build-iso.yaml @@ -22,9 +22,21 @@ jobs: - name: Setup local AUR repo run: | set -euo pipefail + shopt -s nullglob mkdir -p /tmp/localrepo cp localrepo/*.pkg.tar.zst localrepo/local-aur.db* localrepo/local-aur.files* /tmp/localrepo/ sed -i 's|file:///home/alvin/AlvinArch/localrepo|file:///tmp/localrepo|' pacman.conf + # The medium needs its own copy: the ISO's /etc/pacman.conf points + # [local-aur] at file:///opt/localrepo, which pacstrap reads at + # install time to resolve packages that are not in the official repos + # (built during the ISO build and harvested there by + # airootfs/root/customize_airootfs.sh). + mkdir -p airootfs/opt/localrepo + shipped=(localrepo/*.pkg.tar.zst localrepo/local-aur.db* localrepo/local-aur.files*) + if (( ${#shipped[@]} > 0 )); then + cp -L "${shipped[@]}" airootfs/opt/localrepo/ + fi + echo "airootfs/opt/localrepo: $(ls airootfs/opt/localrepo | wc -l) file(s)" - name: Install archiso and dependencies run: | diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fa2b3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Multi-GB mkarchiso output (see AGENTS.md build command) +work/ +out/ + +# In-image copy of localrepo/ — populated by localrepo/update.sh and +# .gitea/workflows/build-iso.yaml, shipped as /opt/localrepo on the ISO +airootfs/opt/localrepo/ diff --git a/AGENTS.md b/AGENTS.md index 1165850..e6b7960 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,9 @@ sudo mkarchiso -v -w work -o out/ . ./localrepo/update.sh pkg1.pkg.tar.zst # add files, then re-index ``` +`update.sh` also refreshes `airootfs/opt/localrepo/` (the copy that ships on the +ISO), so run it after touching `localrepo/` before building. + ## CI Checks (must pass before merge) - **ShellCheck**: all `.sh` files checked with `shellcheck -x -s bash` @@ -32,6 +35,13 @@ sudo mkarchiso -v -w work -o out/ . 2. **`localrepo/*.pkg.tar.zst`** — pre-built AUR packages. The `local-aur` repo is configured in `pacman.conf` and picked up by mkarchiso automatically. 3. **`build-end4-deps.sh`** — builds end-4 AUR deps at build time. +There is a **fourth source**: `customize_airootfs.sh` runs end-4's own +`./setup install-deps` inside the image (as a throwaway `ii-builder` account), +which resolves and compiles everything the dotfiles need (quickshell, MicroTeX, +theming, fonts, ...). Those packages are installed in the live system, land in +`pkglist.x86_64.txt`, and are harvested into `/opt/localrepo` so pacstrap can +install them into the target without the network. + **Important**: `localrepo/update.sh` auto-appends new package names to `packages.x86_64`. If you remove a package from `packages.x86_64` that exists in `localrepo/`, it will be re-added on the next `update.sh` run. ## Key Files @@ -42,14 +52,23 @@ sudo mkarchiso -v -w work -o out/ . | `profiledef.sh` | ISO metadata, compression, boot modes | | `pacman.conf` | Repos: cachyos → core → extra → multilib → chaotic-aur → local-aur | | `bootstrap_packages` | Minimal build-time packages (`base`, `arch-install-scripts`) | -| `airootfs/root/customize_airootfs.sh` | Runs inside chroot during build: keyrings, oh-my-zsh, lazy.nvim, plugins | +| `airootfs/root/customize_airootfs.sh` | Runs inside chroot during build: keyrings, skel dotfiles (incl. end4-pC), qsConfig patch, end-4 `install-deps`, package harvest, uv/font pre-warm | +| `airootfs/root/.live-pacman.conf` | `/etc/pacman.conf` of the built image (adds `[local-aur]` = `file:///opt/localrepo`) | +| `airootfs/usr/local/bin/alvin-install` | Installer: partitions, pacstrap, user, end-4 dotfiles (`--skip-alldeps`), boot | | `localrepo/build-end4-deps.sh` | Non-interactive AUR builds for end-4 dependencies | -| `localrepo/update.sh` | Regenerates localrepo DB + syncs names to packages.x86_64 | +| `localrepo/update.sh` | Regenerates localrepo DB, syncs names to packages.x86_64, syncs `airootfs/opt/localrepo/` | ## Gotchas - The profile root is the **repo root** (not a `releng/` subdirectory). The CI build-iso workflow uses `mkarchiso -v -w work -o out/ releng` but this is a naming convention — the actual profile files live at the repo root. -- `customize_airootfs.sh` clones git repos (oh-my-zsh, lazy.nvim, plugins) into `/etc/skel` during the ISO build. These require network access at build time but make the live ISO work offline. +- `customize_airootfs.sh` clones git repos (oh-my-zsh, lazy.nvim, plugins, end-4 dots) into `/etc/skel` and `/opt` during the ISO build. These require network access at build time but make the live ISO work offline. +- `customize_airootfs.sh` runs `./setup install-deps -f -s --skip-plasmaintg` as the throwaway user `ii-builder` (end-4's scripts refuse to run as root) and aborts the build if it fails. `--skip-plasmaintg` is deliberate: that optional Firefox widget pulls 410 packages / 526 MiB of KDE. - The `[local-aur]` repo path in `pacman.conf` (`file:///home/alvin/alvinarch/localrepo`) is for local builds only. The CI does not use this path. +- The **image** ships its own `[local-aur]` at `file:///opt/localrepo` (see `.live-pacman.conf`). It holds `localrepo/` (copied by CI/`update.sh`) plus everything `install-deps` built. pacstrap runs without `-P`, so the target keeps the stock `/etc/pacman.conf`; `alvin-install` strips `[local-aur]` from it anyway as a safety net, because `/opt/localrepo` exists only on the medium. +- `alvin-install` runs end-4 with `--skip-alldeps` — dependencies must come from the ISO. It feeds `yes i` to end-4's failure prompt: inside the installer chroot the medium's `/run` is bind-mounted in, so end-4's `systemctl --machine=…@.host --user enable … --now` lines can never work (and `--now` would start services on the live medium). `install_end4_dots` re-applies the enable-only part (bluetooth, ydotool, i2c module) outside the chroot and reports every step end-4 had to skip. If the end-4 step reports "incomplete", check that `hyprctl`/`quickshell`/the venv were resolvable (harvest) before re-running anything. +- `/etc/skel/.cache/uv` + `/etc/skel/.local/share/uv/python` are pre-warmed by the build (~460 MB: CPython 3.12 and the pinned wheel cache) so end-4's `uv venv`/`uv pip install` at install time needs no network. +- **end4-pC** (`pctrade/end4-pC`) is the default quickshell config: cloned into `/etc/skel/.config/quickshell/end4-pC` at build time, selected by patching `hl.env("qsConfig", "ii")` → `"end4-pC"` in the **staged** `/opt/dots-hyprland/dots/.config/hypr/hyprland/variables.lua` (there is no `hypr/hyprland/` in this repo — `./setup install` copies it to the installed system). `alvin-install` re-copies end4-pC into the user's home *after* `./setup install`, because end-4's installer `rsync --delete`s all of `~/.config/quickshell`. +- `airootfs/etc/skel/.config/illogical-impulse/presets/` carries the user's presets (`Laptop.json`, `Personal-Performance.json`, `Personal-Performance.zip`). They embed `/home/alvin/...` paths, so wallpapers/screenshots need fixing up after install. - End-4 metapackages are built from `localrepo/end4-pkgbuilds/` (quickshell-git, bibata-modern-classic-bin, microtex-git). - `packages.x86_64` is sorted alphabetically. Keep it sorted when adding/removing entries. +- `airootfs/opt/localrepo/` is a build artifact (gitignored) — never commit it. diff --git a/airootfs/etc/skel/.config/fish/config.fish b/airootfs/etc/skel/.config/fish/config.fish index f10482d..865156c 100644 --- a/airootfs/etc/skel/.config/fish/config.fish +++ b/airootfs/etc/skel/.config/fish/config.fish @@ -23,7 +23,7 @@ if status is-interactive alias celar "printf '\033[2J\033[3J\033[1;1H'" alias claer "printf '\033[2J\033[3J\033[1;1H'" alias pamcan pacman - alias q 'qs -c ii' + alias q 'qs -c end4-pC' if test "$TERM" != "linux" alias ls 'eza --icons' end diff --git a/airootfs/etc/skel/.config/illogical-impulse/presets/Laptop.json b/airootfs/etc/skel/.config/illogical-impulse/presets/Laptop.json new file mode 100644 index 0000000..1ea31fb --- /dev/null +++ b/airootfs/etc/skel/.config/illogical-impulse/presets/Laptop.json @@ -0,0 +1,677 @@ +{ + "appearance": { + "extraBackgroundTint": true, + "fakeScreenRounding": 0, + "fonts": { + "expressive": "Space Grotesk", + "iconNerd": "JetBrains Mono NF", + "main": "Google Sans Flex", + "monospace": "JetBrains Mono NF", + "numbers": "Google Sans Flex", + "reading": "Readex Pro", + "title": "Google Sans Flex" + }, + "palette": { + "accentColor": "", + "type": "scheme-tonal-spot" + }, + "transparency": { + "automatic": true, + "backgroundTransparency": 0.11, + "contentTransparency": 0.57, + "enable": false + }, + "wallpaperTheming": { + "enableAppsAndShell": true, + "enableQtApps": true, + "enableTerminal": true, + "terminalGenerationProps": { + "forceDarkMode": false, + "harmonizeThreshold": 100, + "harmony": 0.5, + "termFgBoost": 0.35 + } + } + }, + "apps": { + "bluetooth": "kcmshell6 kcm_bluetooth", + "changePassword": "kitty -1 --hold=yes fish -i -c 'passwd'", + "manageUser": "kcmshell6 kcm_users", + "network": "kcmshell6 kcm_networkmanagement", + "networkEthernet": "kcmshell6 kcm_networkmanagement", + "taskManager": "plasma-systemmonitor --page-name Processes", + "terminal": "kitty -1", + "update": "kitty -1 --hold=yes fish -i -c 'pkexec pacman -Syu'", + "volumeMixer": "~/.config/hypr/hyprland/scripts/launch_first_available.sh \"pavucontrol-qt\" \"pavucontrol\"" + }, + "background": { + "blurRadius": 32, + "centeredWallpaper": false, + "centeredWallpaperColor": "primaryContainer", + "centeredWallpaperOnlyWhenLocked": false, + "centeredWallpaperShape": "Cookie7Sided", + "centeredWallpaperSize": 400, + "enableWallpaperPreview": false, + "hideWhenFullscreen": true, + "lockWall": "", + "parallax": { + "autoVertical": false, + "enableSidebar": true, + "enableWorkspace": true, + "vertical": false, + "widgetsFactor": 1.2, + "workspaceZoom": 1.07 + }, + "screenList": [], + "showBlur": true, + "showGrid": true, + "showSnapLines": true, + "splitRatio": "25", + "splitSide": "right", + "thumbnailPath": "", + "wallpaperAnimation": "random", + "wallpaperPath": "/home/alvin/Pictures/Wallpapers/Dynamic-Wallpapers/Dark/japan-street-Dark.png", + "widgets": { + "blurRadius": 32, + "blurWidgets": false, + "calendar": { + "enable": true, + "placementStrategy": "free", + "sizeMode": "2x2", + "x": 1596, + "y": 744, + "z": 4 + }, + "clock": { + "color": "", + "cookie": { + "aiStyling": false, + "constantlyRotate": false, + "dateInClock": true, + "dateStyle": "bubble", + "dialNumberStyle": "full", + "hourHandStyle": "hollow", + "hourMarks": false, + "minuteHandStyle": "medium", + "secondHandStyle": "line", + "sides": 12, + "timeIndicators": true, + "useSineCookie": false + }, + "digital": { + "adaptiveAlignment": true, + "animateChange": true, + "font": { + "family": "Google Sans Flex", + "roundness": 1.1290989771359807, + "size": 129.49608904933814, + "weight": 595.725165314564, + "width": 99.44532190132371 + }, + "showDate": true, + "vertical": false + }, + "enable": true, + "pixel": { + "orientation": "vertical" + }, + "placementStrategy": "leastBusy", + "quote": { + "enable": true, + "followClock": false, + "text": "Kawaī neko o zenbu tabe chaimasu." + }, + "showOnlyWhenLocked": false, + "style": "digital", + "styleLocked": "cookie", + "x": 1423, + "y": 383, + "z": 3 + }, + "customImage": { + "enable": true, + "path": "", + "placementStrategy": "free", + "shape": "Cookie4Sided", + "size": 200, + "x": 400, + "y": 100, + "z": 0 + }, + "customText": { + "alignment": "center", + "color": "", + "content": "Hello world", + "enable": true, + "fontFamily": "Caveat", + "fontSize": 72, + "placementStrategy": "free", + "shadow": true, + "x": 400, + "y": 300, + "z": 0 + }, + "images": { + "enable": false, + "placementStrategy": "free", + "x": 400, + "y": 100, + "z": 0 + }, + "media": { + "backgroundShape": "Cookie4Sided", + "enable": true, + "placementStrategy": "free", + "showControls": true, + "showLyrics": false, + "showTitles": true, + "sizeMode": "1x3", + "x": 744, + "y": 876, + "z": 1 + }, + "notes": { + "enable": false, + "placementStrategy": "free", + "x": 400, + "y": 100, + "z": 0 + }, + "resources": { + "enable": false, + "placementStrategy": "free", + "vertical": false, + "x": 400, + "y": 100, + "z": 0 + }, + "shadow": true, + "sticker": { + "enable": false, + "items": [], + "outlineColor": "#ffffff", + "outlineWidth": 8, + "path": "", + "placementStrategy": "free", + "rotation": 0, + "size": 200, + "x": 400, + "y": 100, + "z": 0 + }, + "timers": { + "enable": false, + "placementStrategy": "free", + "vertical": false, + "x": 400, + "y": 100, + "z": 0 + }, + "todo": { + "enable": false, + "placementStrategy": "free", + "x": 400, + "y": 100, + "z": 0 + }, + "userCard": { + "enable": false, + "placementStrategy": "free", + "sizeMode": "1x2", + "x": 400, + "y": 100, + "z": 0 + }, + "visualizer": { + "colorSource": "theme", + "enable": true, + "height": 343, + "placementStrategy": "free", + "ringSize": 380, + "sensitivity": 1.65, + "style": "mirror", + "x": 0, + "y": 0, + "z": -1000 + }, + "weather": { + "enable": false, + "expanded": false, + "placementStrategy": "mostBusy", + "sizeMode": "1x3", + "x": 400, + "y": 100, + "z": 0 + }, + "worldClock": { + "clockCount": 4, + "enable": false, + "placementStrategy": "free", + "sizeMode": "2x2", + "timezones": [ + "Australia/Sydney", + "Asia/Tokyo", + "Europe/London", + "America/New_York" + ], + "x": 400, + "y": 100, + "z": 0 + } + }, + "widgetsLocked": false + }, + "bar": { + "autoHide": { + "enable": false, + "hoverRegionWidth": 2, + "pushWindows": false, + "showWhenPressingSuper": { + "delay": 140, + "enable": true + } + }, + "borderless": "transparent", + "bottom": false, + "centerOnlyReserveFrame": false, + "cornerStyle": 3, + "divider": { + "spacing": 20, + "style": "rect" + }, + "dynamicIsland": { + "leftWidget": "clockWidget", + "rightWidget": "systemIcons", + "showMediaControls": false, + "visualizerStyle": "wave" + }, + "floatStyleShadow": true, + "followFrameColor": false, + "frameColor": "secondaryContainer", + "frameThickness": 4, + "groupColor": "layer1", + "indicators": { + "notifications": { + "showUnreadCount": true + } + }, + "layouts": { + "leftLayout": [ + "leftSidebarButton", + "activeWindow", + "resources" + ], + "middleLayout": [ + "media", + "clockWidget", + "workspaces", + "utilButtons", + "weatherBar", + "batteryIndicator" + ], + "rightLayout": [ + "sysTray", + "updatesCount", + "bluetooth", + "systemIcons" + ] + }, + "media": { + "alwaysVisible": false, + "maxWidth": 280, + "minWidth": 100, + "onlyTitle": false, + "preferredPlayer": "", + "showLyrics": false + }, + "resources": { + "alwaysShowCpu": true, + "alwaysShowCpuTemp": true, + "alwaysShowDisk": false, + "alwaysShowRam": true, + "alwaysShowSwap": false, + "cpuWarningThreshold": 90, + "memoryWarningThreshold": 95, + "showValue": true, + "style": "outline", + "swapWarningThreshold": 85 + }, + "screenList": [], + "showBackground": true, + "showFrame": false, + "tooltips": { + "clickToShow": false, + "enable": true + }, + "topLeftIcon": "spark", + "utilButtons": { + "isRecording": false, + "showColorPicker": false, + "showDarkModeToggle": false, + "showKeyboardToggle": true, + "showMicToggle": true, + "showPerformanceProfileToggle": true, + "showScreenRecord": false, + "showScreenSnip": false, + "showWallpaperToggle": false + }, + "verbose": true, + "vertical": false, + "workspaces": { + "alwaysShowNumbers": true, + "indicatorStyle": "dot", + "monochromeIcons": true, + "numberMap": [], + "showAppIcons": true, + "showNumberDelay": 200, + "shown": 10, + "useNerdFont": false + } + }, + "calendar": { + "locale": "en-GB" + }, + "conflictKiller": { + "autoKillNotificationDaemons": true, + "autoKillTrays": true + }, + "crosshair": { + "code": "0;P;d;1;0l;10;0o;2;1b;0" + }, + "custom": { + "colorizeIcon": true, + "distroIcon": "google-gemini-symbolic" + }, + "dock": { + "enable": true, + "height": 60, + "hoverRegionHeight": 2, + "hoverToReveal": true, + "ignoredAppRegexes": [], + "monochromeIcons": false, + "pinnedApps": [ + "org.kde.dolphin", + "zen" + ], + "pinnedOnStartup": false, + "showAppsButton": true, + "showBackground": true, + "showMedia": true, + "showPinButton": true + }, + "hacks": { + "arbitraryRaceConditionDelay": 20 + }, + "hyprland": { + "animations": { + "animation": "normal", + "enable": true + }, + "decoration": { + "activeOpacity": 1, + "blur": { + "enabled": false, + "passes": 3, + "size": 1 + }, + "inactiveOpacity": 0.95, + "rounding": 22, + "shadow": { + "enabled": true, + "range": 4 + } + }, + "general": { + "borderColor": { + "activeOpacity": 0.47, + "activeRole": "layer0Border", + "enable": false, + "inactiveOpacity": 0.2, + "inactiveRole": "layer0Border" + }, + "borderSize": 1, + "gapsIn": 2, + "gapsOut": 5 + }, + "idle": { + "lock": 300, + "screenOff": 600, + "suspend": 10800 + } + }, + "interactions": { + "deadPixelWorkaround": { + "enable": false + }, + "scrolling": { + "fasterTouchpadScroll": false, + "mouseScrollDeltaThreshold": 120, + "mouseScrollFactor": 120, + "touchpadScrollFactor": 450 + } + }, + "launcher": { + "pinnedApps": [ + "org.kde.dolphin", + "kitty", + "cmake-gui" + ] + }, + "light": { + "antiFlashbang": { + "enable": false + }, + "night": { + "automatic": false, + "colorTemperature": 5559, + "from": "19:00", + "to": "06:30" + } + }, + "lock": { + "blur": { + "enable": false, + "extraZoom": 1.1, + "radius": 100, + "size": 20 + }, + "centerClock": true, + "launchOnStartup": false, + "materialShapeChars": true, + "security": { + "requirePasswordToPower": true, + "unlockKeyring": true + }, + "showLockedText": true, + "showMedia": true, + "showToolbars": true, + "showWidgets": false, + "useHyprlock": false + }, + "media": { + "filterDuplicatePlayers": true + }, + "notifications": { + "position": "top_right", + "timeout": 7000 + }, + "osd": { + "timeout": 3000 + }, + "osk": { + "layout": "qwerty_full", + "pinnedOnStartup": false + }, + "overlay": { + "clickthroughOpacity": 0.8, + "darkenScreen": false, + "floatingImage": { + "imageSource": "https://media.tenor.com/H5U5bJzj3oAAAAAi/kukuru.gif", + "scale": 0.5 + }, + "openingZoomAnimation": true + }, + "overview": { + "centerIcons": true, + "columns": 5, + "enable": true, + "orderBottomUp": false, + "orderRightLeft": false, + "rows": 2, + "scale": 0.18, + "style": "default" + }, + "panelFamily": "ii", + "policies": { + "ai": 1, + "weeb": 1 + }, + "profile": { + "avatarPath": "/home/alvin/Sync_Documents/", + "avatarPicture": "/home/alvin/Sync_Documents/Alvin logo original.png", + "descriptionText": "::uptime::", + "displayName": "", + "onlinePresets": true + }, + "regionSelector": { + "annotation": { + "useSatty": false + }, + "circle": { + "padding": 10, + "strokeWidth": 6 + }, + "rect": { + "showAimLines": true + }, + "targetRegions": { + "content": true, + "contentRegionOpacity": 0.8, + "layers": true, + "opacity": 0.3, + "selectionPadding": 5, + "showLabel": false, + "windows": true + } + }, + "resources": { + "historyLength": 60, + "updateInterval": 3100 + }, + "settings": { + "borderColor": "layer0Border", + "borderSize": 1, + "collapsedSections": [], + "style": "default" + }, + "sidebar": { + "ai": { + "textFadeIn": false + }, + "banner": true, + "bannerImage": "", + "booru": { + "allowNsfw": true, + "defaultProvider": "yandere", + "limit": 20, + "zerochan": { + "username": "theodorer61" + } + }, + "bottomGroup": false, + "cornerOpen": { + "bottom": true, + "bottomLeftAction": "sidebarLeftOpen", + "bottomRightAction": "sidebarRightOpen", + "clickless": false, + "clicklessCornerEnd": true, + "clicklessCornerVerticalOffset": 1, + "cornerRegionHeight": 5, + "cornerRegionWidth": 250, + "enable": true, + "valueScroll": true, + "visualize": false + }, + "keepRightSidebarLoaded": false, + "media": { + "artColors": false, + "artShape": "Oval", + "blurredBackground": false, + "enable": true, + "shapeArt": false, + "showLyrics": true + }, + "mediaPlayer": false, + "quickSliders": { + "enable": true, + "showBrightness": true, + "showMic": true, + "showVolume": true + }, + "quickToggles": { + "android": { + "columns": 6, + "toggles": [ + { + "size": 2, + "type": "network" + }, + { + "size": 2, + "type": "bluetooth" + }, + { + "size": 2, + "type": "audio" + }, + { + "size": 1, + "type": "idleInhibitor" + }, + { + "size": 2, + "type": "mic" + }, + { + "size": 1, + "type": "nightLight" + }, + { + "size": 2, + "type": "powerProfile" + } + ] + }, + "style": "android" + }, + "translator": { + "delay": 300, + "enable": true + } + }, + "tray": { + "filterPassive": true, + "invertPinnedItems": true, + "monochromeIcons": false, + "pinnedItems": [ + "Fcitx" + ], + "showItemId": false + }, + "wallpaperSelector": { + "changeInterval": 0, + "closeAfterSelection": true, + "columns": 4, + "liveWallpapersPath": "", + "showBlurBackground": false, + "showHomePath": true, + "showSearchbar": true, + "sortMode": "time", + "useSystemFileDialog": false, + "userPath": "/home/alvin/Pictures/Wallpapers/" + }, + "windows": { + "centerTitle": true, + "showTitlebar": true + } +} diff --git a/airootfs/etc/skel/.config/illogical-impulse/presets/Personal-Performance.json b/airootfs/etc/skel/.config/illogical-impulse/presets/Personal-Performance.json new file mode 100644 index 0000000..5a0e5be --- /dev/null +++ b/airootfs/etc/skel/.config/illogical-impulse/presets/Personal-Performance.json @@ -0,0 +1,676 @@ +{ + "appearance": { + "extraBackgroundTint": true, + "fakeScreenRounding": 0, + "fonts": { + "expressive": "Space Grotesk", + "iconNerd": "JetBrains Mono NF", + "main": "Google Sans Flex", + "monospace": "JetBrains Mono NF", + "numbers": "Google Sans Flex", + "reading": "Readex Pro", + "title": "Google Sans Flex" + }, + "palette": { + "accentColor": "", + "type": "scheme-tonal-spot" + }, + "transparency": { + "automatic": true, + "backgroundTransparency": 0.11, + "contentTransparency": 0.57, + "enable": false + }, + "wallpaperTheming": { + "enableAppsAndShell": true, + "enableQtApps": true, + "enableTerminal": true, + "terminalGenerationProps": { + "forceDarkMode": false, + "harmonizeThreshold": 100, + "harmony": 0.5, + "termFgBoost": 0.35 + } + } + }, + "apps": { + "bluetooth": "kcmshell6 kcm_bluetooth", + "changePassword": "kitty -1 --hold=yes fish -i -c 'passwd'", + "manageUser": "kcmshell6 kcm_users", + "network": "kcmshell6 kcm_networkmanagement", + "networkEthernet": "kcmshell6 kcm_networkmanagement", + "taskManager": "plasma-systemmonitor --page-name Processes", + "terminal": "kitty -1", + "update": "kitty -1 --hold=yes fish -i -c 'pkexec pacman -Syu'", + "volumeMixer": "~/.config/hypr/hyprland/scripts/launch_first_available.sh \"pavucontrol-qt\" \"pavucontrol\"" + }, + "background": { + "blurRadius": 32, + "centeredWallpaper": false, + "centeredWallpaperColor": "primaryContainer", + "centeredWallpaperOnlyWhenLocked": false, + "centeredWallpaperShape": "Cookie7Sided", + "centeredWallpaperSize": 400, + "enableWallpaperPreview": false, + "hideWhenFullscreen": true, + "lockWall": "", + "parallax": { + "autoVertical": false, + "enableSidebar": true, + "enableWorkspace": true, + "vertical": false, + "widgetsFactor": 1.2, + "workspaceZoom": 1.07 + }, + "screenList": [], + "showBlur": true, + "showGrid": true, + "showSnapLines": true, + "splitRatio": "25", + "splitSide": "right", + "thumbnailPath": "", + "wallpaperAnimation": "random", + "wallpaperPath": "/home/alvin/Pictures/Wallpapers/Dynamic-Wallpapers/Dark/japan-street-Dark.png", + "widgets": { + "blurRadius": 32, + "blurWidgets": false, + "calendar": { + "enable": true, + "placementStrategy": "free", + "sizeMode": "2x2", + "x": 1596, + "y": 744, + "z": 4 + }, + "clock": { + "color": "", + "cookie": { + "aiStyling": false, + "constantlyRotate": false, + "dateInClock": true, + "dateStyle": "bubble", + "dialNumberStyle": "full", + "hourHandStyle": "hollow", + "hourMarks": false, + "minuteHandStyle": "medium", + "secondHandStyle": "line", + "sides": 12, + "timeIndicators": true, + "useSineCookie": false + }, + "digital": { + "adaptiveAlignment": true, + "animateChange": true, + "font": { + "family": "Google Sans Flex", + "roundness": 1.1290989771359807, + "size": 129.49608904933814, + "weight": 350, + "width": 99.44532190132371 + }, + "showDate": true, + "vertical": false + }, + "enable": true, + "pixel": { + "orientation": "vertical" + }, + "placementStrategy": "leastBusy", + "quote": { + "enable": true, + "followClock": false, + "text": "Kawaī neko o zenbu tabe chaimasu." + }, + "showOnlyWhenLocked": false, + "style": "digital", + "styleLocked": "cookie", + "x": 1423, + "y": 383, + "z": 3 + }, + "customImage": { + "enable": true, + "path": "", + "placementStrategy": "free", + "shape": "Cookie4Sided", + "size": 200, + "x": 400, + "y": 100, + "z": 0 + }, + "customText": { + "alignment": "center", + "color": "", + "content": "Hello world", + "enable": true, + "fontFamily": "Caveat", + "fontSize": 72, + "placementStrategy": "free", + "shadow": true, + "x": 400, + "y": 300, + "z": 0 + }, + "images": { + "enable": false, + "placementStrategy": "free", + "x": 400, + "y": 100, + "z": 0 + }, + "media": { + "backgroundShape": "Cookie4Sided", + "enable": true, + "placementStrategy": "free", + "showControls": true, + "showLyrics": false, + "showTitles": true, + "sizeMode": "1x3", + "x": 744, + "y": 876, + "z": 1 + }, + "notes": { + "enable": false, + "placementStrategy": "free", + "x": 400, + "y": 100, + "z": 0 + }, + "resources": { + "enable": false, + "placementStrategy": "free", + "vertical": false, + "x": 400, + "y": 100, + "z": 0 + }, + "shadow": true, + "sticker": { + "enable": false, + "items": [], + "outlineColor": "#ffffff", + "outlineWidth": 8, + "path": "", + "placementStrategy": "free", + "rotation": 0, + "size": 200, + "x": 400, + "y": 100, + "z": 0 + }, + "timers": { + "enable": false, + "placementStrategy": "free", + "vertical": false, + "x": 400, + "y": 100, + "z": 0 + }, + "todo": { + "enable": false, + "placementStrategy": "free", + "x": 400, + "y": 100, + "z": 0 + }, + "userCard": { + "enable": false, + "placementStrategy": "free", + "sizeMode": "1x2", + "x": 400, + "y": 100, + "z": 0 + }, + "visualizer": { + "colorSource": "theme", + "enable": true, + "height": 342, + "placementStrategy": "free", + "ringSize": 380, + "sensitivity": 1.65, + "style": "mirror", + "x": 0, + "y": 0, + "z": -1000 + }, + "weather": { + "enable": false, + "expanded": false, + "placementStrategy": "mostBusy", + "sizeMode": "1x3", + "x": 400, + "y": 100, + "z": 0 + }, + "worldClock": { + "clockCount": 4, + "enable": false, + "placementStrategy": "free", + "sizeMode": "2x2", + "timezones": [ + "Australia/Sydney", + "Asia/Tokyo", + "Europe/London", + "America/New_York" + ], + "x": 400, + "y": 100, + "z": 0 + } + }, + "widgetsLocked": false + }, + "bar": { + "autoHide": { + "enable": false, + "hoverRegionWidth": 2, + "pushWindows": false, + "showWhenPressingSuper": { + "delay": 140, + "enable": true + } + }, + "borderless": "transparent", + "bottom": false, + "centerOnlyReserveFrame": false, + "cornerStyle": 3, + "divider": { + "spacing": 20, + "style": "rect" + }, + "dynamicIsland": { + "leftWidget": "clockWidget", + "rightWidget": "systemIcons", + "showMediaControls": false, + "visualizerStyle": "wave" + }, + "floatStyleShadow": true, + "followFrameColor": false, + "frameColor": "secondaryContainer", + "frameThickness": 4, + "groupColor": "layer1", + "indicators": { + "notifications": { + "showUnreadCount": true + } + }, + "layouts": { + "leftLayout": [ + "leftSidebarButton", + "activeWindow", + "resources" + ], + "middleLayout": [ + "media", + "clockWidget", + "workspaces", + "utilButtons", + "weatherBar" + ], + "rightLayout": [ + "sysTray", + "updatesCount", + "bluetooth", + "systemIcons" + ] + }, + "media": { + "alwaysVisible": false, + "maxWidth": 280, + "minWidth": 100, + "onlyTitle": false, + "preferredPlayer": "", + "showLyrics": false + }, + "resources": { + "alwaysShowCpu": true, + "alwaysShowCpuTemp": true, + "alwaysShowDisk": false, + "alwaysShowRam": true, + "alwaysShowSwap": false, + "cpuWarningThreshold": 90, + "memoryWarningThreshold": 95, + "showValue": true, + "style": "outline", + "swapWarningThreshold": 85 + }, + "screenList": [], + "showBackground": true, + "showFrame": false, + "tooltips": { + "clickToShow": false, + "enable": true + }, + "topLeftIcon": "spark", + "utilButtons": { + "isRecording": false, + "showColorPicker": false, + "showDarkModeToggle": false, + "showKeyboardToggle": false, + "showMicToggle": true, + "showPerformanceProfileToggle": true, + "showScreenRecord": false, + "showScreenSnip": false, + "showWallpaperToggle": false + }, + "verbose": true, + "vertical": false, + "workspaces": { + "alwaysShowNumbers": true, + "indicatorStyle": "dot", + "monochromeIcons": true, + "numberMap": [], + "showAppIcons": true, + "showNumberDelay": 200, + "shown": 10, + "useNerdFont": false + } + }, + "calendar": { + "locale": "en-GB" + }, + "conflictKiller": { + "autoKillNotificationDaemons": true, + "autoKillTrays": true + }, + "crosshair": { + "code": "0;P;d;1;0l;10;0o;2;1b;0" + }, + "custom": { + "colorizeIcon": true, + "distroIcon": "google-gemini-symbolic" + }, + "dock": { + "enable": true, + "height": 60, + "hoverRegionHeight": 2, + "hoverToReveal": true, + "ignoredAppRegexes": [], + "monochromeIcons": false, + "pinnedApps": [ + "org.kde.dolphin", + "zen" + ], + "pinnedOnStartup": false, + "showAppsButton": true, + "showBackground": true, + "showMedia": true, + "showPinButton": true + }, + "hacks": { + "arbitraryRaceConditionDelay": 20 + }, + "hyprland": { + "animations": { + "animation": "normal", + "enable": true + }, + "decoration": { + "activeOpacity": 1, + "blur": { + "enabled": false, + "passes": 3, + "size": 1 + }, + "inactiveOpacity": 0.95, + "rounding": 22, + "shadow": { + "enabled": true, + "range": 4 + } + }, + "general": { + "borderColor": { + "activeOpacity": 0.47, + "activeRole": "layer0Border", + "enable": false, + "inactiveOpacity": 0.2, + "inactiveRole": "layer0Border" + }, + "borderSize": 1, + "gapsIn": 2, + "gapsOut": 5 + }, + "idle": { + "lock": 300, + "screenOff": 600, + "suspend": 10800 + } + }, + "interactions": { + "deadPixelWorkaround": { + "enable": false + }, + "scrolling": { + "fasterTouchpadScroll": false, + "mouseScrollDeltaThreshold": 120, + "mouseScrollFactor": 120, + "touchpadScrollFactor": 450 + } + }, + "launcher": { + "pinnedApps": [ + "org.kde.dolphin", + "kitty", + "cmake-gui" + ] + }, + "light": { + "antiFlashbang": { + "enable": false + }, + "night": { + "automatic": false, + "colorTemperature": 5559, + "from": "19:00", + "to": "06:30" + } + }, + "lock": { + "blur": { + "enable": false, + "extraZoom": 1.1, + "radius": 100, + "size": 20 + }, + "centerClock": true, + "launchOnStartup": false, + "materialShapeChars": true, + "security": { + "requirePasswordToPower": true, + "unlockKeyring": true + }, + "showLockedText": true, + "showMedia": true, + "showToolbars": true, + "showWidgets": false, + "useHyprlock": false + }, + "media": { + "filterDuplicatePlayers": true + }, + "notifications": { + "position": "top_right", + "timeout": 7000 + }, + "osd": { + "timeout": 3000 + }, + "osk": { + "layout": "qwerty_full", + "pinnedOnStartup": false + }, + "overlay": { + "clickthroughOpacity": 0.8, + "darkenScreen": false, + "floatingImage": { + "imageSource": "https://media.tenor.com/H5U5bJzj3oAAAAAi/kukuru.gif", + "scale": 0.5 + }, + "openingZoomAnimation": true + }, + "overview": { + "centerIcons": true, + "columns": 5, + "enable": true, + "orderBottomUp": false, + "orderRightLeft": false, + "rows": 2, + "scale": 0.18, + "style": "default" + }, + "panelFamily": "ii", + "policies": { + "ai": 1, + "weeb": 1 + }, + "profile": { + "avatarPath": "", + "avatarPicture": "", + "descriptionText": "::uptime::", + "displayName": "", + "onlinePresets": false + }, + "regionSelector": { + "annotation": { + "useSatty": false + }, + "circle": { + "padding": 10, + "strokeWidth": 6 + }, + "rect": { + "showAimLines": true + }, + "targetRegions": { + "content": true, + "contentRegionOpacity": 0.8, + "layers": true, + "opacity": 0.3, + "selectionPadding": 5, + "showLabel": false, + "windows": true + } + }, + "resources": { + "historyLength": 60, + "updateInterval": 3100 + }, + "settings": { + "borderColor": "layer0Border", + "borderSize": 1, + "collapsedSections": [], + "style": "default" + }, + "sidebar": { + "ai": { + "textFadeIn": false + }, + "banner": true, + "bannerImage": "", + "booru": { + "allowNsfw": true, + "defaultProvider": "yandere", + "limit": 20, + "zerochan": { + "username": "theodorer61" + } + }, + "bottomGroup": false, + "cornerOpen": { + "bottom": true, + "bottomLeftAction": "sidebarLeftOpen", + "bottomRightAction": "sidebarRightOpen", + "clickless": false, + "clicklessCornerEnd": true, + "clicklessCornerVerticalOffset": 1, + "cornerRegionHeight": 5, + "cornerRegionWidth": 250, + "enable": true, + "valueScroll": true, + "visualize": false + }, + "keepRightSidebarLoaded": false, + "media": { + "artColors": false, + "artShape": "Oval", + "blurredBackground": false, + "enable": true, + "shapeArt": false, + "showLyrics": true + }, + "mediaPlayer": false, + "quickSliders": { + "enable": true, + "showBrightness": true, + "showMic": true, + "showVolume": true + }, + "quickToggles": { + "android": { + "columns": 6, + "toggles": [ + { + "size": 2, + "type": "network" + }, + { + "size": 2, + "type": "bluetooth" + }, + { + "size": 2, + "type": "audio" + }, + { + "size": 1, + "type": "idleInhibitor" + }, + { + "size": 2, + "type": "mic" + }, + { + "size": 1, + "type": "nightLight" + }, + { + "size": 2, + "type": "powerProfile" + } + ] + }, + "style": "android" + }, + "translator": { + "delay": 300, + "enable": true + } + }, + "tray": { + "filterPassive": true, + "invertPinnedItems": true, + "monochromeIcons": false, + "pinnedItems": [ + "Fcitx" + ], + "showItemId": false + }, + "wallpaperSelector": { + "changeInterval": 0, + "closeAfterSelection": true, + "columns": 4, + "liveWallpapersPath": "", + "showBlurBackground": false, + "showHomePath": true, + "showSearchbar": true, + "sortMode": "time", + "useSystemFileDialog": false, + "userPath": "/home/alvin/Pictures/Wallpapers/" + }, + "windows": { + "centerTitle": true, + "showTitlebar": true + } +} diff --git a/airootfs/etc/skel/.config/illogical-impulse/presets/Personal-Performance.zip b/airootfs/etc/skel/.config/illogical-impulse/presets/Personal-Performance.zip new file mode 100644 index 0000000..8dea902 Binary files /dev/null and b/airootfs/etc/skel/.config/illogical-impulse/presets/Personal-Performance.zip differ diff --git a/airootfs/root/.live-pacman.conf b/airootfs/root/.live-pacman.conf index a1db41a..6b6b1da 100644 --- a/airootfs/root/.live-pacman.conf +++ b/airootfs/root/.live-pacman.conf @@ -48,4 +48,13 @@ Include = /etc/pacman.d/cachyos-mirrorlist [chaotic-aur] SigLevel = Required DatabaseOptional -Include = /etc/pacman.d/chaotic-mirrorlist \ No newline at end of file +Include = /etc/pacman.d/chaotic-mirrorlist + +[local-aur] +# Ships on the medium only: packages built at ISO build time (end-4 +# dependencies, plus localrepo/). Keeps pacstrap from needing the network for +# anything that is not in the official repos. alvin-install strips this whole +# section from the target system's pacman.conf, because /opt/localrepo exists +# only on the live medium. +SigLevel = Optional TrustAll +Server = file:///opt/localrepo diff --git a/airootfs/root/customize_airootfs.sh b/airootfs/root/customize_airootfs.sh index 9ca5f60..56c3bb5 100644 --- a/airootfs/root/customize_airootfs.sh +++ b/airootfs/root/customize_airootfs.sh @@ -57,6 +57,181 @@ if [[ ! -d "${NVIM_DATA}/lazy/lazy.nvim" ]]; then https://github.com/folke/lazy.nvim.git "${NVIM_DATA}/lazy/lazy.nvim" fi +# ── end-4 dots-hyprland, staged for alvin-install (avoids a clone at install) ─ +DOTS_REPO="/opt/dots-hyprland" +if [[ ! -d "${DOTS_REPO}/.git" ]]; then + echo "[customize] Cloning end-4 dots-hyprland..." + git clone --depth=1 https://github.com/end-4/dots-hyprland.git "${DOTS_REPO}" + git -C "${DOTS_REPO}" submodule update --init --depth=1 +fi + +# ── end4-pC: the default quickshell config, shipped in skel ────────────────── +# pctrade/end4-pC is a fork of the ii shell config. It lives next to ii in +# ~/.config/quickshell and does not modify it; which one runs is decided by +# qsConfig (patched just below). The repo has no submodules. +if [[ ! -d "${SKEL}/.config/quickshell/end4-pC" ]]; then + echo "[customize] Cloning end4-pC quickshell config into skel..." + git clone --depth=1 https://github.com/pctrade/end4-pC.git \ + "${SKEL}/.config/quickshell/end4-pC" +fi + +# ── Default quickshell config: ii -> end4-pC ───────────────────────────────── +# alvin-install copies this staged repo onto the target and runs `./setup +# install`, whose rsync writes hypr/hyprland/variables.lua into the user's +# home - so patching the staged copy is what sets qsConfig on the installed +# system. Re-running is a no-op; anything other than ii/end4-pC aborts. +VARIABLES_LUA="${DOTS_REPO}/dots/.config/hypr/hyprland/variables.lua" +if grep -Fq 'hl.env("qsConfig", "ii")' "${VARIABLES_LUA}"; then + echo "[customize] Setting qsConfig to end4-pC..." + sed -i 's|hl\.env("qsConfig", "ii")|hl.env("qsConfig", "end4-pC")|' "${VARIABLES_LUA}" +fi +if ! grep -Fq 'hl.env("qsConfig", "end4-pC")' "${VARIABLES_LUA}"; then + echo "[customize] qsConfig is neither ii nor end4-pC in ${VARIABLES_LUA}" >&2 + exit 1 +fi + +# ============================================================================ +# Pre-install end-4 dependencies into the image (network heavy, once per ISO) +# ============================================================================ +# ./setup install-deps resolves and *builds* every AUR/source dependency the +# dotfiles need (quickshell, MicroTeX, theming, fonts, ...). Doing it here lets +# alvin-install run its end-4 step with --skip-alldeps, so an install only +# copies files - no AUR clones, no makepkg, no dependency resolution. +# +# The script refuses to run as root, hence the throwaway builder account. +# ============================================================================ +BUILD_USER="ii-builder" + +if ! id -u "${BUILD_USER}" &>/dev/null; then + useradd -m -u 1500 "${BUILD_USER}" +fi +echo "${BUILD_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99-ii-builder +chmod 440 /etc/sudoers.d/99-ii-builder + +# makepkg writes src/, pkg/ and *.pkg.tar.zst into the PKGBUILD directories, +# so the tree must be owned by the account that runs it. +chown -R "${BUILD_USER}:${BUILD_USER}" "${DOTS_REPO}" + +# -f: never prompt (ask=false); -s: skip "pacman -Syu"; +# --skip-plasmaintg: optional Firefox widget that pulls 410 pkgs / 526 MiB of +# KDE packages onto a non-KDE system; +# stdin: answer end-4's x() failure prompt with "e", so a failed command +# aborts the build instead of blocking forever without a TTY. +# shellcheck disable=SC2024 # the redirect is meant for the child, not sudo +if ! sudo -u "${BUILD_USER}" env HOME="/home/${BUILD_USER}" \ + GIT_TERMINAL_PROMPT=0 \ + bash -c "cd ${DOTS_REPO} && ./setup install-deps -f -s --skip-plasmaintg" \ + < <(yes e); then + echo "[customize] end-4 ./setup install-deps failed" >&2 + exit 1 +fi + +# ── Harvest the packages install-deps just built into the in-image local repo ─ +# The live /etc/pacman.conf points [local-aur] at /opt/localrepo; without the +# harvested copies pacstrap cannot resolve these packages at install time and +# would silently drop them from the target system. +mkdir -p /opt/localrepo +shopt -s nullglob + +# from a package's embedded .PKGINFO (update.sh does the same) +pkg_meta() { + tar -xOf "$1" .PKGINFO 2>/dev/null | + awk -F' = ' '/^pkgname = /{n=$2} /^pkgver = /{v=$2} END{print n, v}' +} + +mapfile -d '' -t built_pkgs < <( + find "${DOTS_REPO}" "/home/${BUILD_USER}/.cache/yay" \ + -name '*.pkg.tar.zst' -print0 2>/dev/null +) + +declare -A built_file=() built_ver=() +for p in "${built_pkgs[@]}"; do + read -r n v < <(pkg_meta "$p") + [[ -n "${n:-}" && -n "${v:-}" ]] || continue + built_file["$n"]="$p" + built_ver["$n"]="$v" +done + +# One entry per package name, and the entry must be the version the image +# actually installed - a stale copy with the same name but another version +# would let pacstrap install something else than what was tested. +for existing in /opt/localrepo/*.pkg.tar.zst; do + [[ -e "$existing" ]] || continue + read -r n v < <(pkg_meta "$existing") + [[ -n "${n:-}" ]] || continue + if [[ -n "${built_ver[$n]:-}" ]]; then + echo "[customize] Replacing localrepo ${n} ${v} with built ${built_ver[$n]}" + rm -f "$existing" + fi +done + +if ((${#built_file[@]} > 0)); then + echo "[customize] Harvesting ${#built_file[@]} package(s) into /opt/localrepo" + cp -f -- "${built_file[@]}" /opt/localrepo/ + # makepkg -Afsi leaves its archives next to the PKGBUILD. They are already + # harvested (and installed in this image); keeping them in the dots tree + # would ship a second copy inside /opt/dots-hyprland, which alvin-install + # copies to ~/.dots-hyprland on every installed system. + find "${DOTS_REPO}" -type f -name '*.pkg.tar.zst' -delete +fi + +# Rebuild the repo database (CI/update.sh ship a pre-built one alongside the +# packages) and refresh the live system's copy of it. No network needed: the +# remote repos were synced during pacstrap. +local_pkgs=(/opt/localrepo/*.pkg.tar.zst) +if ((${#local_pkgs[@]} > 0)); then + echo "[customize] Rebuilding local-aur db (${#local_pkgs[@]} packages)..." + repo-add -R /opt/localrepo/local-aur.db.tar.gz "${local_pkgs[@]}" + mkdir -p /var/lib/pacman/sync + cp -fL /opt/localrepo/local-aur.db /var/lib/pacman/sync/local-aur.db + if [[ -f /opt/localrepo/local-aur.files ]]; then + cp -fL /opt/localrepo/local-aur.files /var/lib/pacman/sync/local-aur.files + fi +fi + +# ── makepkg leftovers + throwaway account do not belong in the ISO ────────── +if [[ -d "${DOTS_REPO}/sdata/dist-arch" ]]; then + find "${DOTS_REPO}/sdata/dist-arch" -mindepth 2 -maxdepth 2 -type d \ + \( -name src -o -name pkg \) -prune -exec rm -rf {} + +fi +rm -f /etc/sudoers.d/99-ii-builder +if ! userdel -r "${BUILD_USER}" 2>/dev/null; then + rm -rf "/home/${BUILD_USER:?}" + userdel "${BUILD_USER}" +fi +chown -R root:root "${DOTS_REPO}" + +# ── Google Sans Flex into skel: install_google_sans_flex() early-returns once +# fc-list sees the font, so the install step needs no network ────────────── +FONT_DIR="${SKEL}/.local/share/fonts/illogical-impulse-google-sans-flex" +if [[ ! -f "${FONT_DIR}/.git/HEAD" ]]; then + echo "[customize] Pre-installing Google Sans Flex into skel..." + rm -rf "${FONT_DIR}" + git clone --depth=1 --recurse-submodules --shallow-submodules \ + https://github.com/end-4/google-sans-flex "${FONT_DIR}" +fi + +# ── uv into skel: CPython 3.12 + the end-4 requirements wheel cache ────────── +# install-python-packages() runs `uv venv -p 3.12` and `uv pip install -r ...` +# during setup. Arch has no python312 package (uv would download one) and the +# pinned requirements are ~250 MiB of wheels, so warm both into skel: the venv +# is then created and filled straight from the image. +if command -v uv &>/dev/null; then + echo "[customize] Pre-warming uv (CPython 3.12 + requirements cache)..." + warm_venv="/tmp/uv-warm-venv" + rm -rf "${warm_venv}" + UV_CACHE_DIR="${SKEL}/.cache/uv" \ + UV_PYTHON_INSTALL_DIR="${SKEL}/.local/share/uv/python" \ + uv venv -p 3.12 "${warm_venv}" + UV_CACHE_DIR="${SKEL}/.cache/uv" \ + UV_PYTHON_INSTALL_DIR="${SKEL}/.local/share/uv/python" \ + uv pip install --python "${warm_venv}/bin/python" \ + -r "${DOTS_REPO}/sdata/uv/requirements.txt" + rm -rf "${warm_venv}" +else + echo "[customize] uv not found - skipping python/wheel pre-warm." +fi + # ── Fish: ensure default shell is set for the live user ────────────────────── # The live user (usually 'archlinux' or 'root') gets fish as default shell if command -v fish &>/dev/null; then diff --git a/airootfs/usr/local/bin/alvin-install b/airootfs/usr/local/bin/alvin-install index 21d83cc..e600cef 100755 --- a/airootfs/usr/local/bin/alvin-install +++ b/airootfs/usr/local/bin/alvin-install @@ -28,6 +28,7 @@ log() { echo "[$(date '+%H:%M:%S')] $*" >> "$LOG"; } cleanup() { set +e + rm -f "${TARGET}/etc/sudoers.d/99-end4-install" 2>/dev/null umount -R "$TARGET" 2>/dev/null cryptsetup close cryptroot 2>/dev/null true @@ -443,7 +444,11 @@ Include = /etc/pacman.d/cachyos-mirrorlist REPO fi - sed -i '/\[local-aur\]/,/Include/d' "${TARGET}/etc/pacman.conf" 2>/dev/null || true + # [local-aur] lives only on the live medium (file:///opt/localrepo). The + # target's pacman.conf does not normally contain it - pacstrap is run + # without -P, so it keeps the stock file shipped by the pacman package - + # but drop it anyway in case that ever changes. + sed -i '/^\[local-aur\]$/,/^$/d' "${TARGET}/etc/pacman.conf" 2>/dev/null || true ok "CachyOS repo configured." } @@ -520,6 +525,183 @@ install_git() { ok "Git configured for ${USERNAME}." } +install_end4_dots() { + if [[ ! -d /opt/dots-hyprland ]]; then + warn "No staged dots-hyprland at /opt/dots-hyprland — skipping end-4 dotfiles." + return + fi + + info "Installing end-4 dotfiles (this may take a while)..." + + local home_in_target="/home/${USERNAME}" + local repo_in_target="${home_in_target}/.dots-hyprland" + local repo_on_host="${TARGET}${repo_in_target}" + local dots_ok=true + local setups_done=true + + cp -a /opt/dots-hyprland "$repo_on_host" + chown -R "${USERNAME}:${USERNAME}" "$repo_on_host" + + echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > "${TARGET}/etc/sudoers.d/99-end4-install" + chmod 440 "${TARGET}/etc/sudoers.d/99-end4-install" + + # Everything appended to $LOG from here belongs to this attempt; used + # further down to report which steps end-4 had to skip. + local log_mark=0 + if [[ -f "$LOG" ]]; then + log_mark=$(wc -l < "$LOG") + fi + + # stdin feeds "i" to end-4's x() [R/e:i] failure prompt, which otherwise + # blocks forever on a TTY whose prompt text is redirected to $LOG. + # Its systemd lines cannot work from inside this chroot: the medium's /run + # is bind-mounted in, so `systemctl --machine=…@.host --user enable … --now` + # has no target session (and --now would start services on the live medium + # anyway) - they fail on every install. "i" lets the run carry on with the + # venv, groups and config sync instead of aborting at the first such line; + # the usable part of that step is re-applied below and every skip is + # reported afterwards. + # --skip-alldeps: every dependency was already installed at ISO build time + # (customize_airootfs.sh runs ./setup install-deps), so this only copies + # configs, sets up services and builds the python venv from the warm cache. + if ! arch-chroot "$TARGET" runuser -u "$USERNAME" -- \ + env HOME="$home_in_target" bash -c \ + "cd ${repo_in_target} && ./setup install -f -s --skip-alldeps" \ + < <(yes i) >>"$LOG" 2>&1; then + warn "end-4 setup failed, retrying files only (see $LOG)." + setups_done=false + arch-chroot "$TARGET" runuser -u "$USERNAME" -- \ + env HOME="$home_in_target" bash -c \ + "cd ${repo_in_target} && ./setup install -f -s --skip-alldeps --skip-allsetups" \ + < <(yes i) >>"$LOG" 2>&1 || dots_ok=false + fi + + rm -f "${TARGET}/etc/sudoers.d/99-end4-install" + + if ! arch-chroot "$TARGET" id -nG "$USERNAME" | grep -qw input; then + arch-chroot "$TARGET" groupadd -f i2c + arch-chroot "$TARGET" usermod -aG video,i2c,input "$USERNAME" + warn "end-4 group setup was missing and has been applied." + fi + + # ── steps end-4 cannot take from inside the chroot ─────────────────────── + # `enable` alone is a pure file operation and needs no running session, so + # redo those here - without --now, since the target is not booted yet. + local mods_conf="${TARGET}/etc/modules-load.d/i2c-dev.conf" + if [[ ! -f "$mods_conf" ]]; then + mkdir -p "$(dirname "$mods_conf")" + echo i2c-dev > "$mods_conf" + fi + + if [[ -f "${TARGET}/usr/lib/systemd/system/bluetooth.service" ]]; then + arch-chroot "$TARGET" systemctl enable bluetooth.service >>"$LOG" 2>&1 \ + || warn "Could not enable bluetooth.service (see $LOG)." + fi + if [[ -f "${TARGET}/usr/lib/systemd/user/ydotool.service" ]]; then + arch-chroot "$TARGET" runuser -u "$USERNAME" -- env HOME="$home_in_target" \ + systemctl --user enable ydotool.service >>"$LOG" 2>&1 \ + || warn "Could not enable ydotool.service for ${USERNAME} (see $LOG)." + fi + + # Everything end-4 answered "i" to. systemctl/gsettings are the lines known + # to be unusable in this chroot; anything else it had to skip is worth + # telling the user about. + local -a ignored=() + if ((log_mark > 0)); then + mapfile -t ignored < <( + sed -n "$((log_mark + 1)),\$p" "$LOG" | sed $'s/\x1b\\[[0-9;]*m//g' | + sed -n 's/^.*Command "\(.*\)" has failed but ignored by user\..*$/\1/p' + ) + fi + if ((${#ignored[@]} > 0)); then + log "end-4 skipped ${#ignored[@]} step(s): ${ignored[*]}" + local step + local -a skipped=() + for step in "${ignored[@]}"; do + case "$step" in + *systemctl* | *gsettings*) ;; + *) skipped+=("$step") ;; + esac + done + if ((${#skipped[@]} > 0)); then + warn "end-4 had to skip: ${skipped[*]} (see $LOG)" + fi + fi + if ! $setups_done; then + warn "end-4 setup did not complete — rerun as ${USERNAME}: cd ~/.dots-hyprland && ./setup install-setups" + fi + + # end-4's install step rsync --delete's the whole ~/.config/quickshell + # directory (sdata/subcmd-install/3.files-legacy.sh), which drops the + # end4-pC fork skel put into the user's home - restore it afterwards. + local fork_src="/etc/skel/.config/quickshell/end4-pC" + local fork_dst="${TARGET}${home_in_target}/.config/quickshell/end4-pC" + if [[ -d "$fork_src" ]]; then + mkdir -p "$(dirname "$fork_dst")" + rm -rf "$fork_dst" + cp -a "$fork_src" "$fork_dst" + chown -R "${USERNAME}:${USERNAME}" "$fork_dst" + fi + + # Same for fish: end-4 syncs ~/.config/fish with rsync --delete, keeping + # only what upstream ships (auto-Hypr.fish, config.fish, fish_variables), + # which drops skel's extras (fisher/nvm functions, completions, + # fish_plugins) and restores its own `alias q 'qs -c ii'`. The default + # quickshell config on this ISO is end4-pC, so put the extras back and + # repoint that one alias. + local fish_src="/etc/skel/.config/fish" + local fish_dst="${TARGET}${home_in_target}/.config/fish" + if [[ -d "$fish_src" && -d "$fish_dst" ]]; then + rsync -a --exclude config.fish --exclude fish_variables \ + "$fish_src/" "$fish_dst/" + if [[ -f "${fish_dst}/config.fish" ]]; then + sed -i "s/alias q 'qs -c ii'/alias q 'qs -c end4-pC'/" "${fish_dst}/config.fish" + grep -qF "alias q 'qs -c end4-pC'" "${fish_dst}/config.fish" \ + || warn "fish 'q' alias still points at the upstream ii config (see $LOG)." + fi + chown -R "${USERNAME}:${USERNAME}" "$fish_dst" + fi + + local configs_ok=true + [[ -e "${TARGET}${home_in_target}/.config/hypr/hyprland.lua" \ + && -d "${TARGET}${home_in_target}/.config/quickshell/ii" \ + && -d "${TARGET}${home_in_target}/.config/quickshell/end4-pC" ]] || configs_ok=false + # Files alone are not enough: with --skip-alldeps the dependencies must + # have come from the ISO. hyprland and quickshell are installed at ISO + # build time, so their binaries missing here means pacstrap dropped them + # (they were not resolvable in the live repos). The venv is created from + # the image's uv cache at install time, so a missing python means the + # python step of end-4's setup never got to run. + if $configs_ok; then + [[ -x "${TARGET}/usr/bin/hyprctl" && -x "${TARGET}/usr/bin/quickshell" ]] \ + || configs_ok=false + [[ -x "${TARGET}${home_in_target}/.local/state/quickshell/.venv/bin/python" ]] \ + || configs_ok=false + fi + + if [[ -d /etc/skel/.config/hypr/custom ]]; then + cp -rfT /etc/skel/.config/hypr/custom "${TARGET}${home_in_target}/.config/hypr/custom" + chown -R "${USERNAME}:${USERNAME}" "${TARGET}${home_in_target}/.config/hypr/custom" + fi + + # Presets ship with absolute paths from the build machine; repoint them at + # the home that was just created (the images they name are not shipped). + local presets="${TARGET}${home_in_target}/.config/illogical-impulse/presets" + local preset + for preset in "$presets"/*.json; do + [[ -f "$preset" ]] || continue + sed -i "s|/home/alvin/|/home/${USERNAME}/|g" "$preset" + done + + if $dots_ok && $configs_ok; then + log "end-4 dotfiles installed for ${USERNAME} (setups_done=${setups_done} skipped=${#ignored[@]})." + ok "end-4 dotfiles installed." + else + log "end-4 dotfiles incomplete for ${USERNAME} (setup_exit_ok=${dots_ok} configs=${configs_ok} setups=${setups_done} skipped=${#ignored[@]})." + warn "end-4 dotfiles incomplete (see $LOG) — as ${USERNAME}, run: cd ~/.dots-hyprland && ./setup install" + fi +} + install_mkinitcpio() { info "Generating initramfs..." @@ -597,6 +779,7 @@ main() { install_network install_user install_git + install_end4_dots install_mkinitcpio install_bootloader install_cleanup diff --git a/localrepo/update.sh b/localrepo/update.sh index a907e1c..5102329 100755 --- a/localrepo/update.sh +++ b/localrepo/update.sh @@ -22,11 +22,26 @@ mapfile -t pkgs < <(find "${repo_dir}" -maxdepth 1 -name '*.pkg.tar.zst' -print if [[ ${#pkgs[@]} -eq 0 ]]; then echo "No .pkg.tar.zst files in ${repo_dir}" >&2 + # keep the medium's copy honest too - an emptied localrepo/ must not + # leave the previous packages shipping on the next build + rm -rf "${profile_dir}/airootfs/opt/localrepo" exit 0 fi repo-add -R "${repo_dir}/${repo_name}.db.tar.gz" "${pkgs[@]}" +# Sync the medium's copy: the ISO ships its own [local-aur] at +# airootfs/opt/localrepo (pacman.conf on the live system points there, and +# pacstrap resolves these packages from it at install time). +img_repo="${profile_dir}/airootfs/opt/localrepo" +mkdir -p "${img_repo}" +rm -f "${img_repo}"/*.pkg.tar.zst "${img_repo}/${repo_name}".db \ + "${img_repo}/${repo_name}".db.tar.gz "${img_repo}/${repo_name}".files \ + "${img_repo}/${repo_name}".files.tar.gz +cp -f "${pkgs[@]}" "${img_repo}/" +cp -fL "${repo_dir}/${repo_name}".db* "${repo_dir}/${repo_name}".files* "${img_repo}/" +echo "Medium copy ready: ${img_repo}" + new_names=() for p in "${pkgs[@]}"; do name=$(tar -xOf "${p}" .PKGINFO | sed -n 's/^pkgname = //p' | head -1)