dot
vizie's NixOS desktop: Hyprland, CachyOS kernel and gaming tweaks
0cf7797
9 files changed +277 −97
modifiedconfig/waybar/eww/eww.scss
@@ -39,18 +39,49 @@ | ||
| 39 | 39 | font-weight: bold; |
| 40 | 40 | } |
| 41 | 41 | |
| 42 | -// ---- tiles (Bluetooth / mouse) ------------------------------------------------ | |
| 42 | +// ---- tiles (Network / Bluetooth, Update NixOS) ----------------------------------- | |
| 43 | 43 | |
| 44 | 44 | .tile { |
| 45 | 45 | background-color: $card; |
| 46 | 46 | border-radius: 18px; |
| 47 | 47 | padding: 10px 12px; |
| 48 | + | |
| 49 | + // Split tile: the padding moves onto the two halves, so each half's hover | |
| 50 | + // highlight is a rounded shape of its own inside the tile. | |
| 51 | + &.split { | |
| 52 | + padding: 4px; | |
| 53 | + } | |
| 48 | 54 | } |
| 49 | 55 | |
| 50 | 56 | .tile-wrap:hover .tile { |
| 51 | 57 | background-color: $card-hover; |
| 52 | 58 | } |
| 53 | 59 | |
| 60 | +.toggle { | |
| 61 | + padding: 6px; | |
| 62 | + border-radius: 14px; | |
| 63 | +} | |
| 64 | + | |
| 65 | +.open { | |
| 66 | + padding: 6px 8px 6px 6px; | |
| 67 | + border-radius: 14px; | |
| 68 | +} | |
| 69 | + | |
| 70 | +.toggle-wrap:hover .toggle, | |
| 71 | +.open-wrap:hover .open { | |
| 72 | + background-color: rgba(255, 255, 255, 0.06); | |
| 73 | +} | |
| 74 | + | |
| 75 | +.chevron { | |
| 76 | + font-size: 14px; | |
| 77 | + color: $dim; | |
| 78 | +} | |
| 79 | + | |
| 80 | +.open-wrap:hover .chevron, | |
| 81 | +.tile-wrap:hover .chevron { | |
| 82 | + color: $fg; | |
| 83 | +} | |
| 84 | + | |
| 54 | 85 | .icon-circle { |
| 55 | 86 | min-width: 38px; |
| 56 | 87 | min-height: 38px; |
@@ -61,6 +92,12 @@ | ||
| 61 | 92 | font-size: 18px; |
| 62 | 93 | } |
| 63 | 94 | |
| 95 | + // The NixOS snowflake sits a little left of centre in the font. | |
| 96 | + .nix { | |
| 97 | + font-size: 19px; | |
| 98 | + padding-left: 2px; | |
| 99 | + } | |
| 100 | + | |
| 64 | 101 | &.on { |
| 65 | 102 | background-color: $fg; |
| 66 | 103 | |
@@ -111,41 +148,6 @@ | ||
| 111 | 148 | min-width: 0; |
| 112 | 149 | min-height: 24px; |
| 113 | 150 | background-color: transparent; |
| 114 | -} | |
| 115 | - | |
| 116 | -// ---- output device switcher ------------------------------------------------------ | |
| 117 | - | |
| 118 | -.device-row { | |
| 119 | - padding: 4px 6px 0 6px; | |
| 120 | - margin: 0 -6px; | |
| 121 | - border-radius: 10px; | |
| 122 | - | |
| 123 | - &:hover { | |
| 124 | - background-color: $card-hover; | |
| 125 | - } | |
| 126 | -} | |
| 127 | - | |
| 128 | -.device-current, | |
| 129 | -.chevron { | |
| 130 | - font-size: 12px; | |
| 131 | - color: $dim; | |
| 132 | -} | |
| 133 | - | |
| 134 | -.device-list { | |
| 135 | - margin-top: 4px; | |
| 136 | -} | |
| 137 | - | |
| 138 | -.device { | |
| 139 | - padding: 6px 8px; | |
| 140 | - border-radius: 10px; | |
| 141 | - | |
| 142 | - &:hover { | |
| 143 | - background-color: $card-hover; | |
| 144 | - } | |
| 145 | - | |
| 146 | - &.active { | |
| 147 | - font-weight: bold; | |
| 148 | - } | |
| 149 | 151 | } |
| 150 | 152 | |
| 151 | 153 | // ---- system rings ----------------------------------------------------------------- |
modifiedconfig/waybar/eww/eww.yuck
@@ -7,13 +7,17 @@ | ||
| 7 | 7 | ;; State |
| 8 | 8 | ;; --------------------------------------------------------------------------- |
| 9 | 9 | |
| 10 | -(defvar sinks_open false) | |
| 11 | 10 | (defvar confirm "none") ;; none | logout | reboot | poweroff |
| 12 | 11 | |
| 13 | 12 | (deflisten audio |
| 14 | - :initial '{"vol":0,"muted":false,"mic":0,"mic_muted":false,"sink":"","sinks":[]}' | |
| 13 | + :initial '{"vol":0,"muted":false,"mic":0,"mic_muted":false}' | |
| 15 | 14 | "python3 $HOME/.config/waybar/eww/scripts/audio.py") |
| 16 | 15 | |
| 16 | +(defpoll net | |
| 17 | + :interval "3s" | |
| 18 | + :initial '{"available":false,"on":false,"connected":false,"icon":"","label":"Unavailable"}' | |
| 19 | + "bash $HOME/.config/waybar/eww/scripts/network.sh") | |
| 20 | + | |
| 17 | 21 | (defpoll mouse |
| 18 | 22 | :interval "30s" |
| 19 | 23 | :initial '{"text":"","percentage":0,"class":"","tooltip":"","label":"Not connected","connected":false}' |
@@ -29,6 +33,12 @@ | ||
| 29 | 33 | :initial "0" |
| 30 | 34 | "df --output=pcent / | tail -1 | tr -dc '0-9'") |
| 31 | 35 | |
| 36 | +;; "26.11 · nixpkgs from 15 Oct", from nixos-version (26.11.20261015.fe13242). | |
| 37 | +(defpoll nixos | |
| 38 | + :interval "10m" | |
| 39 | + :initial "" | |
| 40 | + "v=$(nixos-version | cut -d' ' -f1); date -d \"$(cut -d. -f3 <<<\"$v\")\" \"+$(cut -d. -f1-2 <<<\"$v\") · nixpkgs from %-d %b\"") | |
| 41 | + | |
| 32 | 42 | ;; Last notifications from dunst's history. Only polled while the panel is open; |
| 33 | 43 | ;; scripts/toggle.sh fills it once right before opening. |
| 34 | 44 | (defvar notifs_open false) |
@@ -77,32 +87,42 @@ | ||
| 77 | 87 | (defwidget control-center [] |
| 78 | 88 | (box :class "cc" :orientation "v" :space-evenly false :spacing 10 |
| 79 | 89 | (box :orientation "h" :space-evenly true :spacing 10 |
| 80 | - (tile :icon {bt.on ? "" : ""} | |
| 81 | - :title "Bluetooth" | |
| 82 | - :sub {bt.label} | |
| 83 | - :active {bt.available && bt.on} | |
| 84 | - :onclick "bash $HOME/.config/waybar/eww/scripts/bt-toggle.sh ${bt.on ? 'off' : 'on'}") | |
| 85 | - (tile :icon {mouse.connected ? mouse.text : ""} | |
| 86 | - :title "Superlight" | |
| 87 | - :sub {mouse.label} | |
| 88 | - :active {mouse.connected && mouse.class == "charging"} | |
| 89 | - :onclick ":")) | |
| 90 | + (split-tile :icon {net.icon} | |
| 91 | + :title "Network" | |
| 92 | + :sub {net.label} | |
| 93 | + :active {net.available && net.connected} | |
| 94 | + :ontoggle "bash $HOME/.config/waybar/eww/scripts/net-toggle.sh ${net.on ? 'off' : 'on'}" | |
| 95 | + :onopen "${EWW_CMD} close control-center cc-closer; setsid -f nm-connection-editor") | |
| 96 | + (split-tile :icon {bt.on ? "" : ""} | |
| 97 | + :title "Bluetooth" | |
| 98 | + :sub {bt.label} | |
| 99 | + :active {bt.available && bt.on} | |
| 100 | + :ontoggle "bash $HOME/.config/waybar/eww/scripts/bt-toggle.sh ${bt.on ? 'off' : 'on'}" | |
| 101 | + :onopen "${EWW_CMD} close control-center cc-closer; setsid -f blueman-manager")) | |
| 90 | 102 | (sound) |
| 91 | 103 | (stats) |
| 92 | - (power))) | |
| 104 | + (power) | |
| 105 | + (update))) | |
| 93 | 106 | |
| 94 | 107 | ;; --------------------------------------------------------------------------- |
| 95 | 108 | ;; Building blocks |
| 96 | 109 | ;; --------------------------------------------------------------------------- |
| 97 | 110 | |
| 98 | -(defwidget tile [icon title sub active onclick] | |
| 99 | - (eventbox :class "tile-wrap" :cursor "pointer" :timeout "2s" :onclick onclick | |
| 100 | - (box :class "tile" :orientation "h" :space-evenly false :spacing 10 | |
| 101 | - (box :class "icon-circle ${active ? 'on' : ''}" :space-evenly true | |
| 102 | - (label :text icon)) | |
| 103 | - (box :orientation "v" :valign "center" :halign "start" :space-evenly false | |
| 104 | - (label :class "tile-title" :halign "start" :limit-width 16 :text title) | |
| 105 | - (label :class "tile-sub" :halign "start" :limit-width 18 :text sub))))) | |
| 111 | +;; A tile in two halves: the round icon switches it on and off, the rest opens its | |
| 112 | +;; settings. The halves sit side by side rather than one inside the other, because | |
| 113 | +;; a click on an inner eventbox would reach the outer one too. | |
| 114 | +(defwidget split-tile [icon title sub active ontoggle onopen] | |
| 115 | + (box :class "tile split" :orientation "h" :space-evenly false | |
| 116 | + (eventbox :class "toggle-wrap" :cursor "pointer" :timeout "2s" :onclick ontoggle | |
| 117 | + (box :class "toggle" | |
| 118 | + (box :class "icon-circle ${active ? 'on' : ''}" :space-evenly true | |
| 119 | + (label :text icon)))) | |
| 120 | + (eventbox :class "open-wrap" :cursor "pointer" :timeout "2s" :hexpand true :onclick onopen | |
| 121 | + (box :class "open" :orientation "h" :space-evenly false | |
| 122 | + (box :orientation "v" :valign "center" :halign "start" :hexpand true :space-evenly false | |
| 123 | + (label :class "tile-title" :halign "start" :limit-width 12 :text title) | |
| 124 | + (label :class "tile-sub" :halign "start" :limit-width 15 :text sub)) | |
| 125 | + (label :class "chevron" :valign "center" :text ""))))) | |
| 106 | 126 | |
| 107 | 127 | (defwidget slider-row [icon muted value onmute onchange] |
| 108 | 128 | (box :class "slider-row" :orientation "h" :space-evenly false :spacing 10 |
@@ -112,13 +132,14 @@ | ||
| 112 | 132 | :timeout "1s" :value value :onchange onchange) |
| 113 | 133 | (label :class "slider-val" :text "${value}%"))) |
| 114 | 134 | |
| 115 | -(defwidget ring [value name] | |
| 135 | +;; `text` replaces the percentage in the middle; `hot` (red) defaults to above 85%. | |
| 136 | +(defwidget ring [value name ?text ?hot] | |
| 116 | 137 | (box :class "ring-wrap" :orientation "v" :space-evenly false :spacing 6 :hexpand true |
| 117 | 138 | (overlay :halign "center" |
| 118 | - (circular-progress :class "ring ${value > 85 ? 'hot' : ''}" | |
| 139 | + (circular-progress :class "ring ${(hot ?: value > 85) ? 'hot' : ''}" | |
| 119 | 140 | :value value :thickness 6 :start-at 75 :clockwise true |
| 120 | 141 | :width 60 :height 60) |
| 121 | - (label :class "ring-value" :text "${round(value, 0)}%")) | |
| 142 | + (label :class "ring-value" :text {text ?: "${round(value, 0)}%"})) | |
| 122 | 143 | (label :class "ring-name" :text name))) |
| 123 | 144 | |
| 124 | 145 | (defwidget power-btn [icon label onclick] |
@@ -147,27 +168,18 @@ | ||
| 147 | 168 | :muted {audio.mic_muted} |
| 148 | 169 | :value {audio.mic} |
| 149 | 170 | :onmute "pactl set-source-mute @DEFAULT_SOURCE@ toggle" |
| 150 | - :onchange "pactl set-source-volume @DEFAULT_SOURCE@ {}%") | |
| 151 | - (eventbox :class "device-row" :cursor "pointer" :timeout "1s" | |
| 152 | - :onclick "${EWW_CMD} update sinks_open=${!sinks_open}" | |
| 153 | - (box :orientation "h" :space-evenly false | |
| 154 | - (label :class "device-current" :hexpand true :halign "start" :limit-width 34 | |
| 155 | - :text "Output: ${audio.sink}") | |
| 156 | - (label :class "chevron" :text {sinks_open ? "" : ""}))) | |
| 157 | - (revealer :transition "slidedown" :duration "200ms" :reveal sinks_open | |
| 158 | - (box :class "device-list" :orientation "v" :space-evenly false | |
| 159 | - (for s in {audio.sinks} | |
| 160 | - (eventbox :class "device ${s.active ? 'active' : ''}" :cursor "pointer" :timeout "1s" | |
| 161 | - :onclick "pactl set-default-sink '${s.name}'; ${EWW_CMD} update sinks_open=false" | |
| 162 | - (box :orientation "h" :space-evenly false | |
| 163 | - (label :hexpand true :halign "start" :limit-width 34 :text "${s.desc}") | |
| 164 | - (label :text {s.active ? "" : ""})))))))) | |
| 171 | + :onchange "pactl set-source-volume @DEFAULT_SOURCE@ {}%"))) | |
| 165 | 172 | |
| 166 | 173 | (defwidget stats [] |
| 167 | 174 | (box :class "card" :orientation "h" :space-evenly true |
| 168 | 175 | (ring :value {EWW_CPU.avg} :name "CPU") |
| 169 | 176 | (ring :value {EWW_RAM.used_mem_perc} :name "RAM") |
| 170 | - (ring :value disk :name "Disk"))) | |
| 177 | + (ring :value disk :name "Disk") | |
| 178 | + ;; The Superlight's battery; red when low rather than when full. | |
| 179 | + (ring :value {mouse.connected ? mouse.percentage : 0} | |
| 180 | + :text {mouse.connected ? "${mouse.percentage}%" : "–"} | |
| 181 | + :hot {mouse.class == "warning" || mouse.class == "critical"} | |
| 182 | + :name {mouse.class == "charging" ? "Charging" : "Mouse"}))) | |
| 171 | 183 | |
| 172 | 184 | (defwidget power [] |
| 173 | 185 | (box :class "card" :orientation "v" :space-evenly false :spacing 6 |
@@ -192,10 +204,23 @@ | ||
| 192 | 204 | (box :class "btn" |
| 193 | 205 | (label :text "Cancel"))) |
| 194 | 206 | (eventbox :class "btn-wrap" :cursor "pointer" :timeout "3s" |
| 195 | - :onclick "${EWW_CMD} update confirm=none sinks_open=false; ${EWW_CMD} close control-center cc-closer; ${confirm == 'reboot' ? 'systemctl reboot' : (confirm == 'poweroff' ? 'systemctl poweroff' : 'hyprctl dispatch exit')}" | |
| 207 | + :onclick "${EWW_CMD} update confirm=none; ${EWW_CMD} close control-center cc-closer; ${confirm == 'reboot' ? 'systemctl reboot' : (confirm == 'poweroff' ? 'systemctl poweroff' : 'hyprctl dispatch exit')}" | |
| 196 | 208 | (box :class "btn danger" |
| 197 | 209 | (label :text "Confirm"))))))) |
| 198 | 210 | |
| 211 | +;; Updates and reboots in a kitty window (tools/nixos-update), asking before it | |
| 212 | +;; installs anything. | |
| 213 | +(defwidget update [] | |
| 214 | + (eventbox :class "tile-wrap" :cursor "pointer" :timeout "3s" | |
| 215 | + :onclick "${EWW_CMD} close control-center cc-closer; setsid -f kitty --title 'NixOS update' nixos-update" | |
| 216 | + (box :class "tile" :orientation "h" :space-evenly false :spacing 10 | |
| 217 | + (box :class "icon-circle" :space-evenly true | |
| 218 | + (label :class "nix" :text "")) | |
| 219 | + (box :orientation "v" :valign "center" :halign "start" :hexpand true :space-evenly false | |
| 220 | + (label :class "tile-title" :halign "start" :text "Update NixOS") | |
| 221 | + (label :class "tile-sub" :halign "start" :limit-width 34 :text nixos)) | |
| 222 | + (label :class "chevron" :valign "center" :text "")))) | |
| 223 | + | |
| 199 | 224 | ;; --------------------------------------------------------------------------- |
| 200 | 225 | ;; Notifications panel |
| 201 | 226 | ;; --------------------------------------------------------------------------- |
@@ -41,15 +41,6 @@ | ||
| 41 | 41 | : , |
| 42 | 42 | : |
| 43 | 43 | : , |
| 44 | - : , | |
| 45 | - : | |
| 46 | - | |
| 47 | - | |
| 48 | - | |
| 49 | - | |
| 50 | - | |
| 51 | - | |
| 52 | -, | |
| 53 | 44 | } |
| 54 | 45 | |
| 55 | 46 | |
@@ -1,0 +1,6 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# Usage: net-toggle.sh on|off -- switches networking and refreshes the tile right away. | |
| 3 | +DIR="" | |
| 4 | + | |
| 5 | + | |
| 6 | + |
@@ -1,0 +1,36 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# Network state as one line of JSON for the Eww control center. Wired wins over | |
| 3 | +# Wi-Fi when both are up. | |
| 4 | +ICON_WIRED=$'\U000F0200' # nf-md-ethernet | |
| 5 | +ICON_WIFI=$'\U000F05A9' # nf-md-wifi | |
| 6 | +ICON_OFF=$'\U000F0202' # nf-md-ethernet_off | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | +|| { ; ; } | |
| 13 | +networking= || { ; ; } | |
| 14 | +[ || { ; ; } | |
| 15 | + | |
| 16 | +# DEVICE:TYPE:STATE:CONNECTION | |
| 17 | +devices= | |
| 18 | +wired= | |
| 19 | +wifi= | |
| 20 | + | |
| 21 | +if [; then | |
| 22 | + dev= | |
| 23 | + speed= | |
| 24 | + label="Wired" | |
| 25 | + if [ ; then | |
| 26 | + label+=" · Gb/s" | |
| 27 | + elif [ ; then | |
| 28 | + label+=" · Mb/s" | |
| 29 | + fi | |
| 30 | + | |
| 31 | +elif [; then | |
| 32 | + name= | |
| 33 | + | |
| 34 | +else | |
| 35 | + | |
| 36 | + |
@@ -35,7 +35,7 @@ | ||
| 35 | 35 | && open+=("") |
| 36 | 36 | done |
| 37 | 37 | [ && |
| 38 | - | |
| 38 | + | |
| 39 | 39 | |
| 40 | 40 | |
| 41 | 41 | # Clicking the button of the panel that is open (or asking to close) just closes it; |
modifiedmodules/home/packages.nix
@@ -1,8 +1,24 @@ | ||
| 1 | 1 | # Programs your configs and scripts call, and your own tools. |
| 2 | -{ inputs, pkgs, ... }: | |
| 2 | +{ | |
| 3 | + config, | |
| 4 | + inputs, | |
| 5 | + pkgs, | |
| 6 | + ... | |
| 7 | +}: | |
| 3 | 8 | let |
| 4 | 9 | system = pkgs.stdenv.hostPlatform.system; |
| 5 | 10 | |
| 11 | + # The control centre's "Update NixOS" tile (tools/nixos-update). | |
| 12 | + nixos-update = pkgs.writeShellApplication { | |
| 13 | + name = "nixos-update"; | |
| 14 | + runtimeInputs = with pkgs; [ | |
| 15 | + git | |
| 16 | + nvd | |
| 17 | + ]; | |
| 18 | + runtimeEnv.DOT_REPO = config.dot.repoPath; | |
| 19 | + text = builtins.readFile ../../tools/nixos-update; | |
| 20 | + }; | |
| 21 | + | |
| 6 | 22 | # Philly's webview (WebKitGTK) closes at once on the NVIDIA driver under |
| 7 | 23 | # Wayland, with "Error 71 (Protocol error) dispatching to Wayland display", |
| 8 | 24 | # unless its DMA-BUF renderer is off. Set on the program itself, so it holds |
@@ -37,6 +53,8 @@ | ||
| 37 | 53 | # Called by the control centre and tray scripts. |
| 38 | 54 | pulseaudio # pactl |
| 39 | 55 | bluez # bluetoothctl |
| 56 | + networkmanagerapplet # nm-connection-editor, the Network tile's settings | |
| 57 | + nixos-update | |
| 40 | 58 | solaar # mouse battery fallback (mouse-battery.sh) |
| 41 | 59 | |
| 42 | 60 | # Screenshots and screen recording (imsh-shot, imsh-cast; Super+Shift+S/F/R). |
modifiedmodules/home/services.nix
@@ -92,12 +92,6 @@ | ||
| 92 | 92 | slice = "session-graphical.slice"; |
| 93 | 93 | }; |
| 94 | 94 | |
| 95 | - nm-applet = graphical { | |
| 96 | - description = "NetworkManager tray icon"; | |
| 97 | - exec = "/bin/nm-applet --indicator"; | |
| 98 | - after = [ "waybar.service" ]; | |
| 99 | - }; | |
| 100 | - | |
| 101 | 95 | # Puts Wine/Proton tray icons (Battle.net) into Waybar's tray, so it needs |
| 102 | 96 | # the tray to be there first. |
| 103 | 97 | xembedsniproxy = graphical { |
@@ -142,9 +136,14 @@ | ||
| 142 | 136 | }; |
| 143 | 137 | }; |
| 144 | 138 | |
| 145 | - # The Bluetooth tray icon (blueman-applet) comes from the autostart entry | |
| 146 | - # services.blueman installs, which uwsm also runs as a systemd unit | |
| 147 | - # (app-blueman@autostart.service). | |
| 139 | + # Network and Bluetooth live in the control centre, not the tray. blueman-applet | |
| 140 | + # still runs, from the autostart entry services.blueman installs (uwsm runs it | |
| 141 | + # as app-blueman@autostart.service), for pairing prompts and notifications; | |
| 142 | + # only its tray icon is turned off (ShowConnected needs that icon). | |
| 143 | + dconf.settings."org/blueman/general".plugin-list = [ | |
| 144 | + "!StatusIcon" | |
| 145 | + "!ShowConnected" | |
| 146 | + ]; | |
| 148 | 147 | |
| 149 | 148 | # Bitwarden at login. This one stays an XDG autostart entry, as on CachyOS |
| 150 | 149 | # (uwsm runs those as systemd units too: app-bitwarden@autostart.service); the |
addedtools/nixos-update
@@ -1,0 +1,103 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# Updates NixOS from a throwaway worktree of this repo, so the checkout you edit in | |
| 3 | +# is left alone until the update is done. Started from the control centre's | |
| 4 | +# "Update NixOS" tile in a kitty window (installed as `nixos-update`, | |
| 5 | +# modules/home/packages.nix, which sets DOT_REPO). | |
| 6 | +# | |
| 7 | +# 1. a temporary worktree of main | |
| 8 | +# 2. nix flake update, committed there | |
| 9 | +# 3. nixos-rebuild build, then nvd's list of what changed against the running system | |
| 10 | +# 4. asks before going on; then pushes the commit, installs it as the next boot | |
| 11 | +# entry (nixos-rebuild boot), brings your checkout's main up to date and reboots | |
| 12 | +# | |
| 13 | +# If anything fails, the window stays open until you press Enter, and nothing is | |
| 14 | +# rebooted. | |
| 15 | +set -E | |
| 16 | + | |
| 17 | +repo=${DOT_REPO:-$HOME/git/dot} | |
| 18 | +tmp="" | |
| 19 | + | |
| 20 | +step() { printf '\n\e[1;34m==> %s\e[0m\n' "$*"; } | |
| 21 | +note() { printf '\e[1;33m%s\e[0m\n' "$*"; } | |
| 22 | +wait_close() { read -rp "Press Enter to close. " _ || true; } | |
| 23 | + | |
| 24 | +die() { | |
| 25 | + trap - ERR | |
| 26 | + printf '\n\e[1;31m%s\e[0m\n' "$*" | |
| 27 | + wait_close | |
| 28 | + exit 1 | |
| 29 | +} | |
| 30 | + | |
| 31 | +cleanup() { | |
| 32 | + if [ -n "$tmp" ]; then | |
| 33 | + git -C "$repo" worktree remove --force "$tmp" 2>/dev/null || true | |
| 34 | + rm -rf "$tmp" | |
| 35 | + tmp="" | |
| 36 | + fi | |
| 37 | +} | |
| 38 | + | |
| 39 | +trap 'die "The update failed (see above). Nothing was rebooted."' ERR | |
| 40 | +trap cleanup EXIT | |
| 41 | + | |
| 42 | +step "Fetching $repo" | |
| 43 | +git -C "$repo" worktree prune | |
| 44 | +git -C "$repo" fetch origin main | |
| 45 | +git -C "$repo" merge-base --is-ancestor origin/main main || | |
| 46 | + die "main in $repo is behind origin/main (or has diverged); pull first." | |
| 47 | +base=$(git -C "$repo" rev-parse main) | |
| 48 | +ahead=$(git -C "$repo" rev-list --count origin/main..main) | |
| 49 | +dirty=$(git -C "$repo" status --porcelain) | |
| 50 | + | |
| 51 | +tmp=$(mktemp -d -t dot-update.XXXXXX) | |
| 52 | +step "Creating a worktree of main in $tmp" | |
| 53 | +git -C "$repo" worktree add --detach "$tmp" main | |
| 54 | +cd "$tmp" || die "Could not enter $tmp." | |
| 55 | + | |
| 56 | +step "Updating flake inputs" | |
| 57 | +nix flake update --commit-lock-file | |
| 58 | +if [ "$(git rev-parse HEAD)" = "$base" ]; then | |
| 59 | + printf '\nEverything is already up to date.\n' | |
| 60 | + wait_close | |
| 61 | + exit 0 | |
| 62 | +fi | |
| 63 | + | |
| 64 | +step "Building" | |
| 65 | +nixos-rebuild build --flake "$tmp" | |
| 66 | + | |
| 67 | +step "What changes" | |
| 68 | +git --no-pager log -1 --format=%b | |
| 69 | +nvd diff /run/current-system "$tmp/result" | |
| 70 | + | |
| 71 | +echo | |
| 72 | +[ -z "$dirty" ] || note "Uncommitted changes in $repo are not part of this update." | |
| 73 | +[ "$ahead" -eq 0 ] || note "main has $ahead commit(s) not on GitHub yet; they are pushed too." | |
| 74 | +read -rp "Commit, push, install and reboot? [y/N] " answer || answer="" | |
| 75 | +case $answer in | |
| 76 | + [yY]*) ;; | |
| 77 | + *) | |
| 78 | + echo "Nothing was changed." | |
| 79 | + sleep 1 | |
| 80 | + exit 0 | |
| 81 | + ;; | |
| 82 | +esac | |
| 83 | + | |
| 84 | +step "Pushing" | |
| 85 | +git push origin HEAD:main | |
| 86 | + | |
| 87 | +step "Installing as the next boot entry" | |
| 88 | +nixos-rebuild boot --sudo --flake "$tmp" | |
| 89 | + | |
| 90 | +step "Updating main in $repo" | |
| 91 | +commit=$(git rev-parse HEAD) | |
| 92 | +if [ "$(git -C "$repo" symbolic-ref --quiet --short HEAD)" = main ]; then | |
| 93 | + git -C "$repo" merge --ff-only "$commit" | |
| 94 | +else | |
| 95 | + git -C "$repo" update-ref refs/heads/main "$commit" "$base" | |
| 96 | +fi | |
| 97 | +cd "$repo" | |
| 98 | +cleanup | |
| 99 | + | |
| 100 | +trap 'printf "\nReboot cancelled. The update boots next time.\n"; wait_close; exit 0' INT | |
| 101 | +step "Rebooting in 5 seconds (Ctrl+C to cancel)" | |
| 102 | +sleep 5 | |
| 103 | +systemctl reboot |