Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ There is no config file; it is flags and environment, so it works the same in a
| `NIXDATIFIER_GITHUB_TOKEN_FILE` | file holding a GitHub token, used when `GITHUB_TOKEN` is unset (config key `github_token_file`). |
| `NIXDATIFIER_CHECK_TARBALLS` | `off` skips tarball flake inputs when checking for updates (config key `check_tarballs`; on by default) |
| `NIXDATIFIER_DEEP_INPUTS` | `on` checks every transitive flake input, not just the root (and wrapper) inputs (config key `deep_inputs`; off by default) |
| `NIXDATIFIER_MEASURE_STORE` | `off` skips the slow store measurement; `disk` and the window then show only free space until you ask (`disk --refresh`, or Measure in the window; config key `measure_store`; on by default) |
| `NIXDATIFIER_IGNORE_INPUTS` | comma-separated input names (globs allowed) to leave out of update checks (config key `ignored_inputs`; empty by default) |
| `NIXDATIFIER_GITHUB_USE_GH` | `on` borrows the login of a signed-in `gh` when there is no token (config key `github_use_gh`; off by default, its token often has wide scopes) |

Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ reason; the widget also shows a notification when an upstream cannot be reached.
| **Show delete generation action** | `true` | Shows Delete on generation cards. |
| **Show update notifications** | `true` | Desktop notifications for new flake updates and finished actions. |
| **Refresh generations when the popup opens** | `true` | Reloads generations and system info each time you open the popup. |
| **Measure the Nix store** | `true` | Walks the store for its size and the estimated reclaimable space. Off shows free space only until you click Measure (also `measure_store` in the CLI/TUI config). |
| **Check flake inputs in the background** | `true` | Turns the update checks on or off. |
| **Deployed secrets directory** | `""` | Empty detects `/run/secrets` or `/run/agenix.d`. |
| **Encrypted source secrets path** | `""` | Empty detects a SOPS file in the flake directory. |
Expand Down
1 change: 1 addition & 0 deletions hyprland/NixdatifierShell.qml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ShellRoot {
property bool enableHostDetect: true
property bool checkTarballs: true
property bool deepInputCheck: false
property bool measureStore: true
property string ignoredInputs: ""
property string customCommands: "[{\"label\":\"update\",\"cmd\":\"nix flake update\"},{\"label\":\"upnix\",\"cmd\":\"upnix\"}]"
property bool showCommandButtons: true
Expand Down
4 changes: 4 additions & 0 deletions package/contents/config/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
<!-- check every transitive input, not just the root (and wrapper) inputs -->
<default>false</default>
</entry>
<entry name="measureStore" type="Bool">
<!-- measure the Nix store (total and reclaimable size); off shows free space only -->
<default>true</default>
</entry>
<entry name="ignoredInputs" type="String">
<!-- input names to skip when checking for updates, comma separated, globs allowed -->
<default></default>
Expand Down
19 changes: 12 additions & 7 deletions package/contents/tools/sh/nixdatifier
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ help_entry() {
generations) echo "generations [--limit N]#system generations, newest first#Marks the running (ACTIVE) and booted generation. JSON has storePath, nixosVersion and kernel." ;;
updates) echo "updates [--all] [--check]#flake inputs behind upstream#Probes each input's remote (cached for the check interval, 1h by default, and shared with the window; --refresh checks again). --all also lists up-to-date and pinned inputs. --check exits 2 when an update exists, for cron and CI." ;;
diff) echo "diff [--config] [A [B]]#what changed between generations (default: newest vs previous)#Package changes with sizes. --config shows the changes in your .nix files instead (needs recorded snapshots, see: help snapshot)." ;;
disk) echo "disk [STORE-PATH]#store size and what GC could free; or why a path is kept#With a store path: its GC roots, referrers and closure size." ;;
disk) echo "disk [STORE-PATH]#store size and what GC could free; or why a path is kept#With a store path: its GC roots, referrers and closure size. With measure_store off, only free space is shown; --refresh measures once." ;;
health) echo "health#release/EOL, reboot needed, failed units, binary caches#Read only. Works on any distro with Nix; NixOS-only checks are left out elsewhere." ;;
status) echo "status [--updates]#one-line summary for prompts and status bars#Running generation, reboot needed, failed units. --updates adds a (cached) upstream check. --json for scripts." ;;
history) echo "history [list|clear]#what earlier switches, cleanups and updates did#Shared with the window: the last 50 runs, including their output (--json). clear forgets them (asks first)." ;;
Expand Down Expand Up @@ -126,14 +126,14 @@ unknown_name() {
# Saved settings (nixdatifier config): they fill in what the environment leaves
# empty, so a variable or option on the command line still wins.
CONFIG_FILE=${NIXDATIFIER_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/nixdatifier/config}
CONFIG_KEYS="flake config_repo secrets secrets_source persist priv commands check_interval notifications github_token_file github_use_gh check_tarballs deep_inputs ignored_inputs"
CONFIG_KEYS="flake config_repo secrets secrets_source persist priv commands check_interval notifications github_token_file github_use_gh check_tarballs deep_inputs ignored_inputs measure_store"
config_var() { # config_var KEY: the environment variable a key stands for
case $1 in flake) echo NIXDATIFIER_FLAKE ;; config_repo) echo NIXDATIFIER_CONFIG_REPO ;; secrets) echo NIXDATIFIER_SECRETS ;;
secrets_source) echo NIXDATIFIER_SECRETS_SOURCE ;; persist) echo NIXDATIFIER_PERSIST ;; priv) echo NIXDATIFIER_PRIV ;;
commands) echo NIXDATIFIER_COMMANDS ;; check_interval) echo NIXDATIFIER_CHECK_INTERVAL ;; notifications) echo NIXDATIFIER_NOTIFICATIONS ;;
github_token_file) echo NIXDATIFIER_GITHUB_TOKEN_FILE ;; github_use_gh) echo NIXDATIFIER_GITHUB_USE_GH ;;
check_tarballs) echo NIXDATIFIER_CHECK_TARBALLS ;; deep_inputs) echo NIXDATIFIER_DEEP_INPUTS ;;
ignored_inputs) echo NIXDATIFIER_IGNORE_INPUTS ;; esac
ignored_inputs) echo NIXDATIFIER_IGNORE_INPUTS ;; measure_store) echo NIXDATIFIER_MEASURE_STORE ;; esac
}
config_file_get() { [[ -f $CONFIG_FILE ]] && sed -n "s/^$1=//p" "$CONFIG_FILE" | tail -1; return 0; }
COMMANDS_FROM_ENV=${NIXDATIFIER_COMMANDS:+1}
Expand Down Expand Up @@ -454,6 +454,11 @@ cmd_disk() {
kv '"path\t\(.path)", "exists\t\(.exists)", "closure\t\(.closureBytes | hb)", (.roots[] | "root\t\(.)"), (.referrers[] | "referrer\t\(.)")'
return
fi
if [[ ${NIXDATIFIER_MEASURE_STORE:-} == off ]] && ((!refresh)); then # the slow walk is off unless asked for (--refresh): only the free space
free=$("$here/diskusage" --free) || die 'could not read the free space'
jq -n --argjson f "$free" '{freeBytes: $f}' | kv '"free\t\(.freeBytes | hb)", "note\tstore measurement is off; disk --refresh measures once"'
return
fi
IFS=$'\x1e' read -r total reclaimable free stamp < <(NOTE='Measuring the store…' cached disk:nar-v1 1800 -- "$here/diskusage") || die 'could not measure the store'
jq -n --argjson t "$total" --argjson r "$reclaimable" --argjson f "$free" --argjson s "${stamp:-0}" \
'{storeBytes: $t, reclaimableBytes: $r, freeBytes: $f, measuredAt: $s}' |
Expand Down Expand Up @@ -967,7 +972,7 @@ cmd_persist() {
# The window keeps these settings in its own store (KDE config, a JSON file),
# so it mirrors them into the config file through `config sync`: the terminal
# view and the window then show and change the same values. GUI name:file key.
GUI_MAP="flakePath:flake configRepoPath:config_repo secretsPath:secrets secretsSourcePath:secrets_source impermanencePaths:persist checkInterval:check_interval customCommands:commands showNotifications:notifications githubTokenFile:github_token_file githubUseGh:github_use_gh checkTarballs:check_tarballs deepInputCheck:deep_inputs ignoredInputs:ignored_inputs"
GUI_MAP="flakePath:flake configRepoPath:config_repo secretsPath:secrets secretsSourcePath:secrets_source impermanencePaths:persist checkInterval:check_interval customCommands:commands showNotifications:notifications githubTokenFile:github_token_file githubUseGh:github_use_gh checkTarballs:check_tarballs deepInputCheck:deep_inputs ignoredInputs:ignored_inputs measureStore:measure_store"
config_file_put() { # config_file_put KEY VALUE: set it, or remove it when empty
mkdir -p "$(dirname "$CONFIG_FILE")" || die "cannot create $(dirname "$CONFIG_FILE")"
{ grep -v "^$1=" "$CONFIG_FILE" 2>/dev/null; [[ -z $2 ]] || echo "$1=$2"; } >"$CONFIG_FILE.new" && mv "$CONFIG_FILE.new" "$CONFIG_FILE"
Expand Down Expand Up @@ -1000,7 +1005,7 @@ gui_sync() { # gui_sync pull|push JSON: JSON holds the window's current values
showNotifications) case $v in true) v=on ;; false) v=off ;; *) v='' ;; esac
[[ $mode == pull && $v == on ]] && v='' ;; # on is the default everywhere
githubUseGh | deepInputCheck) case $v in true) v=on ;; *) v='' ;; esac ;; # off is the default
checkTarballs) case $v in false) v=off ;; *) v='' ;; esac ;; # on is the default
checkTarballs | measureStore) case $v in false) v=off ;; *) v='' ;; esac ;; # on is the default
customCommands) v=$(commands_normalize "$v") || v=''
[[ $mode == pull && $v == '[]' ]] && v='' ;;
esac
Expand All @@ -1020,7 +1025,7 @@ gui_sync() { # gui_sync pull|push JSON: JSON holds the window's current values
showNotifications) [[ $v == on || $v == off ]] || continue; out=$(jq -c --arg g "$g" --argjson v "$([[ $v == on ]] && echo true || echo false)" '.[$g] = $v' <<<"$out") ;;
# Always answer these, so clearing the file's value restores the default in the window.
githubUseGh | deepInputCheck) out=$(jq -c --arg g "$g" --argjson v "$([[ $v == on ]] && echo true || echo false)" '.[$g] = $v' <<<"$out") ;;
checkTarballs) out=$(jq -c --arg g "$g" --argjson v "$([[ $v == off ]] && echo false || echo true)" '.[$g] = $v' <<<"$out") ;;
checkTarballs | measureStore) out=$(jq -c --arg g "$g" --argjson v "$([[ $v == off ]] && echo false || echo true)" '.[$g] = $v' <<<"$out") ;;
customCommands) [[ -n $v ]] || continue; out=$(jq -c --arg g "$g" --arg v "$v" '.[$g] = $v' <<<"$out") ;;
*) out=$(jq -c --arg g "$g" --arg v "$v" '.[$g] = $v' <<<"$out") ;;
esac
Expand All @@ -1046,7 +1051,7 @@ cmd_config() {
[[ $key != commands ]] || v=$(commands_normalize "$v") || usage_error 'commands is a JSON list: [{"label":"update","cmd":"nix flake update"}]'
[[ $key != notifications || -z $v || $v =~ ^(on|off)$ ]] || usage_error 'notifications takes on or off'
[[ $key != github_use_gh || -z $v || $v =~ ^(on|off)$ ]] || usage_error 'github_use_gh takes on or off'
[[ $key != check_tarballs && $key != deep_inputs || -z $v || $v =~ ^(on|off)$ ]] || usage_error "$key takes on or off"
[[ $key != check_tarballs && $key != deep_inputs && $key != measure_store || -z $v || $v =~ ^(on|off)$ ]] || usage_error "$key takes on or off"
[[ $key != check_interval || -z $v || $v =~ ^[1-9][0-9]*[mhd]$ ]] || usage_error 'check_interval takes minutes, hours or days: 30m, 6h, 1d'
mkdir -p "$(dirname "$CONFIG_FILE")" || die "cannot create $(dirname "$CONFIG_FILE")"
{ grep -v "^$key=" "$CONFIG_FILE" 2>/dev/null; [[ -z $v ]] || echo "$key=$v"; } >"$CONFIG_FILE.new" && mv "$CONFIG_FILE.new" "$CONFIG_FILE"
Expand Down
1 change: 1 addition & 0 deletions package/contents/tools/sh/tui
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,7 @@ settings_desc() { # settings_desc KEY: what it is
github_use_gh) echo 'on: borrow the GitHub CLI login (gh auth token) for release notes when no token file is set. Its token often has wide scopes, so it is off unless you switch it on. Empty: off.' ;;
check_tarballs) echo 'off: skip tarball flake inputs (channels, .tar.zst) when checking for updates, here and in the window. Empty: on.' ;;
deep_inputs) echo 'on: check every transitive flake input, not just the root (and wrapper) inputs. Costs one request per input. Empty: off.' ;;
measure_store) echo 'off: do not measure the Nix store (total and reclaimable size); free space only until you ask (disk --refresh, or Measure in the window). Empty: on.' ;;
ignored_inputs) echo 'Flake inputs to skip when checking for updates: names separated by commas, globs allowed (nur, my-private/*). Empty: check them all.' ;;
theme) echo 'Colour theme: deep, marina, midnight, full, plain or your own (see docs/cli.md). Enter cycles.' ;;
layout) echo 'roomy: boxed sections with the logo. compact: no boxes. Enter switches.' ;;
Expand Down
3 changes: 2 additions & 1 deletion package/contents/ui/ApplicationView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ FullView {
diskReclaimableBytes: engine.diskReclaimableBytes
diskFreeBytes: engine.diskFreeBytes
diskUsageError: engine.diskUsageError
measureStore: engine.settings.measureStore
isProbingDiskUsage: engine._diskProbeRunning
hashResult: engine.hashResult
isProbingHash: engine.isProbingHash
Expand Down Expand Up @@ -103,7 +104,7 @@ FullView {
onCompareRequested: (a, b) => engine.comparePair(a, b)
onDiffViewModeChanged: engine.settings.diffViewMode = diffViewMode
onRefreshRequested: () => engine.refreshGenerations()
onDiskUsageRefreshRequested: () => engine.probeDiskUsage(true)
onDiskUsageRefreshRequested: () => engine.probeDiskUsage(true, true)
onCheckFlakeRequested: () => engine.checkFlakeUpdates()
onSelectGen: n => engine.loadGenDetails(n)
onCollapseGen: () => engine.selectedGenNum = -1
Expand Down
24 changes: 21 additions & 3 deletions package/contents/ui/Engine.qml
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,16 @@ Item {
property bool _diskProbeDirty: false
property string diskUsageError: ""

function probeDiskUsage(force) {
// `manual`: the person asked for it (the Measure button). With measuring
// switched off, only that walks the store; everything else reads free space.
property bool _diskProbeManual: false
function probeDiskUsage(force, manual) {
if (!root.settings.measureStore && !manual && !root._diskProbeManual) {
root.probeFreeDiskSpace();
return;
}
if (manual)
root._diskProbeManual = true;
if (force)
root._diskProbeDirty = true;
if (root._diskProbeRunning)
Expand All @@ -498,17 +507,19 @@ Item {
if (root.autoStart && !root.expanded)
return;
const refresh = root._diskProbeDirty;
const manualRun = root._diskProbeManual;
if (!refresh && root._lastDiskRequestMs > 0 && Date.now() - root._lastDiskRequestMs < 5000)
return;
root._diskProbeDirty = false;
root._diskProbeManual = false;
root._diskProbeRunning = true;
root._lastDiskRequestMs = Date.now();
sh(shq(root.scriptDir + "run") + " cached disk:nar-v1 " + (refresh ? 0 : Math.round(root.diskProbeTtlMs / 1000)) + " -- " + shq(root.scriptDir + "diskusage"), function (cmd, out, err, code) {
root._diskProbeRunning = false;
// A mutation/forced refresh arrived during this snapshot. Do not
// display it or lose the request; run one fresh probe afterward.
if (root._diskProbeDirty) {
root.probeDiskUsage(true);
root.probeDiskUsage(true, manualRun);
return;
}
const p = (out || "").trim().split("\x1e");
Expand Down Expand Up @@ -2767,7 +2778,7 @@ Item {
property bool syncSettings: false
property bool _syncReady: false
property bool _syncApplying: false
readonly property var _syncKeys: ["flakePath", "configRepoPath", "secretsPath", "secretsSourcePath", "githubTokenFile", "githubUseGh", "checkTarballs", "deepInputCheck", "ignoredInputs", "impermanencePaths", "checkInterval", "customCommands", "showNotifications"]
readonly property var _syncKeys: ["flakePath", "configRepoPath", "secretsPath", "secretsSourcePath", "githubTokenFile", "githubUseGh", "checkTarballs", "deepInputCheck", "measureStore", "ignoredInputs", "impermanencePaths", "checkInterval", "customCommands", "showNotifications"]
function _syncSnapshot() {
const values = {};
for (const key of root._syncKeys)
Expand Down Expand Up @@ -2854,6 +2865,13 @@ Item {
function onDeepInputCheckChanged() {
root.schedulePushSharedSettings();
}
function onMeasureStoreChanged() {
root.schedulePushSharedSettings();
if (root.settings.measureStore)
root.probeDiskUsage(true);
else
root.probeFreeDiskSpace();
}
function onIgnoredInputsChanged() {
root.schedulePushSharedSettings();
root.recheckAfterListChange();
Expand Down
Loading
Loading