Last active
July 3, 2025 08:52
-
-
Save antifuchs/10138c4d838a63c0a05e725ccd7bccdd to your computer and use it in GitHub Desktop.
A nix module that arranges the macOS dock the way you want it. Note: It won't allow you to manually re-arrange the items on it; the dock gets reset everytime you log in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ config, pkgs, lib, ... }: | |
with lib; | |
let | |
cfg = config.local.dock; | |
stdenv = pkgs.stdenv; | |
in | |
{ | |
options = { | |
local.dock.enable = mkOption { | |
description = "Enable dock"; | |
default = stdenv.isDarwin; | |
example = false; | |
}; | |
local.dock.entries = mkOption | |
{ | |
description = "Entries on the Dock"; | |
type = with types; listOf (submodule { | |
options = { | |
path = lib.mkOption { type = str; }; | |
section = lib.mkOption { | |
type = str; | |
default = "apps"; | |
}; | |
options = lib.mkOption { | |
type = str; | |
default = ""; | |
}; | |
}; | |
}); | |
readOnly = true; | |
}; | |
}; | |
config = | |
mkIf (cfg.enable) | |
( | |
let | |
dockutil = (import ./dockutil.nix); | |
du = "env PYTHONIOENCODING=utf-8 ${dockutil}/bin/dockutil"; | |
normalize = path: if hasSuffix ".app" path then path + "/" else path; | |
entryURI = path: "file://" + (builtins.replaceStrings | |
# TODO: This is entirely too naive and works only with the bundles that I have seen on my system so far: | |
[" " "!" "\"" "#" "$" "%" "&" "'" "(" ")"] | |
["%20" "%21" "%22" "%23" "%24" "%25" "%26" "%27" "%28" "%29"] | |
(normalize path) | |
); | |
wantURIs = concatMapStrings | |
(entry: "${entryURI entry.path}\n") | |
cfg.entries; | |
createEntries = concatMapStrings | |
(entry: "${du} --no-restart --add '${entry.path}' --section ${entry.section} ${entry.options}\n") | |
cfg.entries; | |
in | |
{ | |
system.activationScripts.postUserActivation.text = '' | |
echo >&2 "Setting up persistent dock items..." | |
haveURIs="$(${du} --list | ${pkgs.coreutils}/bin/cut -f2)" | |
if ! diff -wu <(echo -n "$haveURIs") <(echo -n '${wantURIs}') >&2 ; then | |
echo >&2 "Resetting Dock." | |
${du} --no-restart --remove all | |
${createEntries} | |
killall Dock | |
else | |
echo >&2 "Dock is how we want it." | |
fi | |
''; | |
} | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
with (import <nixpkgs> { }); | |
derivation { | |
name = "dockutil-2.0.5"; | |
builder = "${bash}/bin/bash"; | |
args = [ | |
"-xeuc" | |
'' | |
${unzip}/bin/unzip $src | |
${coreutils}/bin/mkdir -p $out/bin | |
${coreutils}/bin/mv dockutil-2.0.5/scripts/dockutil $out/bin/dockutil | |
'' | |
]; | |
src = fetchurl { | |
url = "https://github.com/kcrawford/dockutil/archive/2.0.5.zip"; | |
sha256 = "0b18awdaimf3gc4dhxx6lpivvx4li7j8kci648ssz39fwmbknlam"; | |
}; | |
system = builtins.currentSystem; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ config, pkgs, ... }: | |
{ | |
local.dock.entries = [ | |
{ path = "${pkgs.emacs}/Applications/Emacs.app/"; } | |
{ path = "/Applications/Mailplane.app"; } | |
{ path = "/Applications/IRCCloud.app/"; } | |
{ path = "/Applications/Google Chrome.app/"; } | |
{ path = "/Applications/iPulse.app/"; } | |
{ path = "/Applications/Dash.app/"; } | |
{ path = "/System/Applications/Messages.app/"; } | |
{ path = "/Applications/iTerm.app/"; } | |
{ path = "/System/Applications/Music.app/"; } | |
{ path = "/System/Applications/Home.app/"; } | |
# Folders: | |
{ | |
path = "/Users/asf/Downloads/"; | |
section = "others"; | |
options = "--sort dateadded --view grid --display folder"; | |
} | |
{ | |
path = "/Users/asf/Mess/Mess/"; | |
section = "others"; | |
options = "--sort name --view grid --display folder"; | |
} | |
]; | |
} |
postUserActivation
was removed, so here's an updated version for 25.05 using postActivation
{
config,
pkgs,
lib,
...
}:
# Original source: https://gist.github.com/antifuchs/10138c4d838a63c0a05e725ccd7bccdd
with lib;
let
cfg = config.local.dock;
inherit (pkgs) stdenv dockutil;
in
{
options = {
local.dock.enable = mkOption {
description = "Enable dock";
default = stdenv.isDarwin;
example = false;
};
local.dock.entries = mkOption {
description = "Entries on the Dock";
type =
with types;
listOf (submodule {
options = {
path = lib.mkOption { type = str; };
section = lib.mkOption {
type = str;
default = "apps";
};
options = lib.mkOption {
type = str;
default = "";
};
};
});
readOnly = true;
};
local.dock.username = mkOption {
description = "Username to apply the dock settings to";
default = config.system.primaryUser;
type = types.str;
};
};
config = mkIf cfg.enable (
let
normalize = path: if hasSuffix ".app" path then path + "/" else path;
entryURI =
path:
"file://"
+ (builtins.replaceStrings
[
" "
"!"
"\""
"#"
"$"
"%"
"&"
"'"
"("
")"
]
[
"%20"
"%21"
"%22"
"%23"
"%24"
"%25"
"%26"
"%27"
"%28"
"%29"
]
(normalize path)
);
wantURIs = concatMapStrings (entry: "${entryURI entry.path}\n") cfg.entries;
createEntries = concatMapStrings (
entry:
"${dockutil}/bin/dockutil --no-restart --add '${entry.path}' --section ${entry.section} ${entry.options}\n"
) cfg.entries;
in
{
system.activationScripts.postActivation.text = ''
echo >&2 "Setting up the Dock for ${cfg.username}..."
su ${cfg.username} -s /bin/sh <<'USERBLOCK'
haveURIs="$(${dockutil}/bin/dockutil --list | ${pkgs.coreutils}/bin/cut -f2)"
if ! diff -wu <(echo -n "$haveURIs") <(echo -n '${wantURIs}') >&2 ; then
echo >&2 "Resetting Dock."
${dockutil}/bin/dockutil --no-restart --remove all
${createEntries}
killall Dock
else
echo >&2 "Dock setup complete."
fi
USERBLOCK
'';
}
);
}
and usage is now:
# Fully declarative dock using the latest from Nix Store
local.dock.enable = true;
local.dock.entries = [
# installed from brew/App Store
{ path = "/Applications/Ghostty.app/"; }
{ path = "/Applications/Brave Browser.app/"; }
{ path = "/Applications/Slack.app/"; }
{ path = "/System/Applications/System Settings.app"; }
{
path = "/Users/alice/Downloads/";
section = "others";
}
];
# defaults to config.system.primaryUser
local.dock.username = "alice";
local.dock.username = mkOption { description = "Username to apply the dock settings to"; type = types.str; }; };
That's a good catch - you can use default = config.system.primaryUser;
there, so you don't have to set that manually.
local.dock.username = mkOption { description = "Username to apply the dock settings to"; type = types.str; }; };
That's a good catch - you can use
default = config.system.primaryUser;
there, so you don't have to set that manually.
Good point! Updated my Gist 👍
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have modified dock.nix to add spacers and folders