Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57a3171f94 | ||
|
|
c1403be257 | ||
|
|
b4cccbd4bc | ||
|
|
1543ae0852 | ||
|
|
a4cf1d1085 | ||
|
|
d5bd9cd77a | ||
|
|
b2217f8051 | ||
|
|
08920bcfd1 | ||
|
|
320cbf535b | ||
|
|
deadc7204c | ||
|
|
a1fa429e94 | ||
|
|
d8a6661f78 | ||
|
|
5138eaf896 | ||
|
|
e636bf1664 | ||
|
|
ee9c7b96c9 | ||
|
|
cd7cf09aa3 | ||
|
|
ee20edc445 | ||
|
|
bb9c29c193 | ||
|
|
08278afff6 | ||
|
|
aabb2037ed | ||
|
|
0bddd7a5ed | ||
|
|
9f8122dd2b | ||
|
|
818b88b75f | ||
|
|
d0d0978f34 | ||
|
|
ace757665e | ||
|
|
d9ffa378a6 | ||
|
|
b317d77bbf | ||
|
|
f73cbf1f65 | ||
|
|
958c16ba9a | ||
|
|
4bcdd687ca | ||
|
|
3f0f0ece39 | ||
|
|
57ff7cbd20 | ||
|
|
6a771120d6 | ||
|
|
f8531f95fe | ||
|
|
7c3d11b160 | ||
|
|
8073b91aa3 | ||
|
|
ae3e2b18d3 | ||
|
|
82325d90a1 |
@@ -156,7 +156,7 @@
|
||||
|
||||
`nix.daemonIONice` was renamed to `nix.daemonIOLowPriority`, and
|
||||
`nix.daemonNiceLevel` was removed in favor a new option
|
||||
`nix.nix.daemonProcessType`.
|
||||
`nix.daemonProcessType`.
|
||||
|
||||
2021-01-16
|
||||
- Added `homebrew` module, to manage formulas installed by Homebrew via `brew bundle`.
|
||||
|
||||
@@ -39,8 +39,8 @@ cd /etc/nix-darwin
|
||||
|
||||
# To use Nixpkgs unstable:
|
||||
nix flake init -t nix-darwin/master
|
||||
# To use Nixpkgs 25.11:
|
||||
nix flake init -t nix-darwin/nix-darwin-25.11
|
||||
# To use Nixpkgs 26.05:
|
||||
nix flake init -t nix-darwin/nix-darwin-26.05
|
||||
|
||||
sed -i '' "s/simple/$(scutil --get LocalHostName)/" flake.nix
|
||||
```
|
||||
@@ -60,9 +60,9 @@ Add the following to `flake.nix` in the same folder as `configuration.nix`:
|
||||
description = "John's darwin system";
|
||||
|
||||
inputs = {
|
||||
# Use `github:NixOS/nixpkgs/nixpkgs-25.11-darwin` to use Nixpkgs 25.11.
|
||||
# Use `github:NixOS/nixpkgs/nixpkgs-26.05-darwin` to use Nixpkgs 26.05.
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
# Use `github:nix-darwin/nix-darwin/nix-darwin-25.11` to use Nixpkgs 25.11.
|
||||
# Use `github:nix-darwin/nix-darwin/nix-darwin-26.05` to use Nixpkgs 26.05.
|
||||
nix-darwin.url = "github:nix-darwin/nix-darwin/master";
|
||||
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
@@ -88,8 +88,8 @@ Unlike NixOS, `nix-darwin` does not have an installer, you can just run `darwin-
|
||||
```bash
|
||||
# To use Nixpkgs unstable:
|
||||
sudo nix run nix-darwin/master#darwin-rebuild -- switch
|
||||
# To use Nixpkgs 25.11:
|
||||
sudo nix run nix-darwin/nix-darwin-25.11#darwin-rebuild -- switch
|
||||
# To use Nixpkgs 26.05:
|
||||
sudo nix run nix-darwin/nix-darwin-26.05#darwin-rebuild -- switch
|
||||
```
|
||||
|
||||
### Step 3. Using `nix-darwin`
|
||||
@@ -132,8 +132,8 @@ Copy the [simple](./modules/examples/simple.nix) example to `/etc/nix-darwin/con
|
||||
```bash
|
||||
# If you use Nixpkgs unstable (the default):
|
||||
sudo nix-channel --add https://github.com/nix-darwin/nix-darwin/archive/master.tar.gz darwin
|
||||
# If you use Nixpkgs 25.11:
|
||||
sudo nix-channel --add https://github.com/nix-darwin/nix-darwin/archive/nix-darwin-25.11.tar.gz darwin
|
||||
# If you use Nixpkgs 26.05:
|
||||
sudo nix-channel --add https://github.com/nix-darwin/nix-darwin/archive/nix-darwin-26.05.tar.gz darwin
|
||||
|
||||
sudo nix-channel --update
|
||||
```
|
||||
|
||||
@@ -94,8 +94,7 @@ in rec {
|
||||
--stylesheet highlightjs/mono-blue.css \
|
||||
--script ./highlightjs/highlight.pack.js \
|
||||
--script ./highlightjs/loader.js \
|
||||
--toc-depth 1 \
|
||||
--chunk-toc-depth 1 \
|
||||
--sidebar-depth 1 \
|
||||
./manual.md \
|
||||
$dst/index.html
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1765934234,
|
||||
"narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=",
|
||||
"lastModified": 1783279667,
|
||||
"narHash": "sha256-/NAkDSsve+GNM0Bt6tleJdCGfsTlK89nPjkVOzZMo0s=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "af84f9d270d404c17699522fab95bbf928a2d92f",
|
||||
"rev": "f205b5574fd0cb7da5b702a2da51507b7f4fdd1b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -42,16 +42,17 @@
|
||||
with pkgs.pkgsBuildBuild;
|
||||
[
|
||||
alacritty
|
||||
alacritty-graphics
|
||||
ghostty-bin
|
||||
kitty
|
||||
mtm
|
||||
rio
|
||||
rxvt-unicode-unwrapped
|
||||
rxvt-unicode-unwrapped-emoji
|
||||
st
|
||||
termite
|
||||
tmux
|
||||
wezterm
|
||||
] ++ lib.optional (pkgs ? ghostty-bin) ghostty-bin
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
+88
-27
@@ -151,7 +151,7 @@ let
|
||||
};
|
||||
description = ''
|
||||
Extra environment variables to set when {command}`nix-darwin` invokes
|
||||
{command}`brew bundle [install]` during system activation.
|
||||
{command}`brew bundle [install]` during system checks and activation.
|
||||
|
||||
Useful for setting Homebrew's `HOMEBREW_NO_*` variables (e.g.,
|
||||
`HOMEBREW_NO_ENV_HINTS`, `HOMEBREW_NO_ANALYTICS`, `HOMEBREW_NO_UPDATE_REPORT_NEW`)
|
||||
@@ -172,18 +172,31 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
brewBundleCmd = mkInternalOption { type = types.str; };
|
||||
brewBundleCmd = mkInternalOption { type = types.functionTo types.str; };
|
||||
};
|
||||
|
||||
config = {
|
||||
brewBundleCmd = concatStringsSep " " (
|
||||
optional (!config.autoUpdate) "HOMEBREW_NO_AUTO_UPDATE=1"
|
||||
brewBundleCmd = { onlyCheck }: concatStringsSep " " (
|
||||
[
|
||||
''PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH"''
|
||||
"sudo"
|
||||
"--preserve-env=PATH"
|
||||
"--user=${escapeShellArg cfg.user}"
|
||||
"--set-home"
|
||||
"env"
|
||||
]
|
||||
++ optional (onlyCheck || !config.autoUpdate) "HOMEBREW_NO_AUTO_UPDATE=1"
|
||||
++ mapAttrsToList (k: v: "${k}=${escapeShellArg v}") config.extraEnv
|
||||
++ [ "brew bundle --file='${brewfileFile}'" ]
|
||||
++ optional (!config.upgrade) "--no-upgrade"
|
||||
++ optional (config.cleanup == "uninstall") "--cleanup"
|
||||
++ optional (config.cleanup == "zap") "--cleanup --zap"
|
||||
++ config.extraFlags
|
||||
++ (
|
||||
if onlyCheck then
|
||||
[ "cleanup 2>&1" ]
|
||||
else
|
||||
optional (!config.upgrade) "--no-upgrade"
|
||||
++ optional (config.cleanup == "uninstall") "--force-cleanup"
|
||||
++ optional (config.cleanup == "zap") "--zap --force-cleanup"
|
||||
++ config.extraFlags
|
||||
)
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -274,6 +287,21 @@ let
|
||||
for current behavior.
|
||||
'';
|
||||
};
|
||||
trusted = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Whether to trust this tap during {command}`nix-darwin` system activation, by adding the
|
||||
`trusted: true` option to its {command}`brew bundle` Brewfile entry.
|
||||
|
||||
Homebrew 6.0.0 enabled `HOMEBREW_REQUIRE_TAP_TRUST` by default, which refuses to
|
||||
load formulae/casks from non-official taps that haven't been trusted, aborting activation.
|
||||
Set this to `true` for non-official taps you control so their formulae and casks are
|
||||
installed during activation. Official taps are always trusted, so this has no effect only
|
||||
them.
|
||||
'';
|
||||
};
|
||||
|
||||
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
|
||||
};
|
||||
@@ -287,7 +315,8 @@ let
|
||||
"tap ${sCfg.name}"
|
||||
+ optionalString (sCfg ? clone_target) ", ${sCfg.clone_target}"
|
||||
+ optionalString (sCfg ? force_auto_update)
|
||||
", force_auto_update: ${sCfg.force_auto_update}";
|
||||
", force_auto_update: ${sCfg.force_auto_update}"
|
||||
+ optionalString config.trusted ", trusted: true";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -514,6 +543,27 @@ let
|
||||
# `version_file` is intentionally not exposed: it writes the installed version to a file
|
||||
# path relative to the `brew bundle` working directory, which is not meaningful during
|
||||
# nix-darwin system activation.
|
||||
trusted = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = ''
|
||||
Whether to trust this formula during {command}`nix-darwin` system activation, by adding the
|
||||
`trusted: true` option to its {command}`brew bundle` Brewfile entry.
|
||||
|
||||
Homebrew 6.0.0 enabled `HOMEBREW_REQUIRE_TAP_TRUST` by default, which refuses to
|
||||
load formulae from non-official taps that haven't been trusted, aborting activation. Set
|
||||
this to `true` for a formula from a non-official tap you control so it is installed during
|
||||
activation.
|
||||
|
||||
This only takes effect when {option}`name` is a fully-qualified name (`user/repo/formula`),
|
||||
since only fully-qualified names map to a tap and can be trusted (and installed) on their
|
||||
own. When {option}`name` is a plain formula name, it is resolved through your tapped
|
||||
repositories, so trust must instead come from the containing tap being marked as trusted
|
||||
(see [](#opt-homebrew.taps)). Official taps are always trusted, so this has no effect on
|
||||
them.
|
||||
'';
|
||||
};
|
||||
|
||||
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
|
||||
};
|
||||
@@ -521,14 +571,15 @@ let
|
||||
config =
|
||||
let
|
||||
sCfg = mkProcessedSubmodConfig config;
|
||||
sCfgSubset = removeAttrs sCfg [ "name" "restart_service" "link" ];
|
||||
sCfgSubset = removeAttrs sCfg [ "name" "restart_service" "link" "trusted" ];
|
||||
in
|
||||
{
|
||||
brewfileLine =
|
||||
"brew ${sCfg.name}"
|
||||
+ optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}"
|
||||
+ mkBrewfileLineBoolOrSymbolString "link" config sCfg
|
||||
+ mkBrewfileLineBoolOrSymbolString "restart_service" config sCfg;
|
||||
+ mkBrewfileLineBoolOrSymbolString "restart_service" config sCfg
|
||||
+ optionalString config.trusted ", trusted: true";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -562,6 +613,27 @@ let
|
||||
on every {command}`brew bundle` run.
|
||||
'';
|
||||
};
|
||||
trusted = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = ''
|
||||
Whether to trust this cask during {command}`nix-darwin` system activation, by adding the
|
||||
`trusted: true` option to its {command}`brew bundle` Brewfile entry.
|
||||
|
||||
Homebrew 6.0.0 enabled `HOMEBREW_REQUIRE_TAP_TRUST` by default, which refuses to
|
||||
load casks from non-official taps that haven't been trusted, silently skipping them. Set
|
||||
this to `true` for a cask from a non-official tap you control so it is installed during
|
||||
activation.
|
||||
|
||||
This only takes effect when {option}`name` is a fully-qualified name (`user/repo/cask`),
|
||||
since only fully-qualified names map to a tap and can be trusted (and installed) on their
|
||||
own. When {option}`name` is a plain cask name, it is resolved through your tapped
|
||||
repositories, so trust must instead come from the containing tap being marked as trusted
|
||||
(see [](#opt-homebrew.taps)). Official taps are always trusted, so this has no effect on
|
||||
them.
|
||||
'';
|
||||
};
|
||||
|
||||
brewfileLine = mkInternalOption { type = types.nullOr types.str; };
|
||||
};
|
||||
@@ -569,12 +641,13 @@ let
|
||||
config =
|
||||
let
|
||||
sCfg = mkProcessedSubmodConfig config;
|
||||
sCfgSubset = removeAttrs sCfg [ "name" ];
|
||||
sCfgSubset = removeAttrs sCfg [ "name" "trusted" ];
|
||||
in
|
||||
{
|
||||
brewfileLine =
|
||||
"cask ${sCfg.name}"
|
||||
+ optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}";
|
||||
+ optionalString (sCfgSubset != { }) ", ${mkBrewfileLineOptionsListString sCfgSubset}"
|
||||
+ optionalString config.trusted ", trusted: true";
|
||||
};
|
||||
};
|
||||
in
|
||||
@@ -935,13 +1008,7 @@ in
|
||||
system.checks.text = mkIf (cfg.enable && cfg.onActivation.cleanup == "check") ''
|
||||
if [ -f "${cfg.prefix}/bin/brew" ]; then
|
||||
homebrewCleanupExitCode=0
|
||||
homebrewCleanupResult=$(PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
|
||||
sudo \
|
||||
--preserve-env=PATH \
|
||||
--user=${escapeShellArg cfg.user} \
|
||||
--set-home \
|
||||
env HOMEBREW_NO_AUTO_UPDATE=1 \
|
||||
brew bundle cleanup --file='${brewfileFile}' 2>&1) || homebrewCleanupExitCode=$?
|
||||
homebrewCleanupResult=$(${cfg.onActivation.brewBundleCmd { onlyCheck = true; }}) || homebrewCleanupExitCode=$?
|
||||
if [ "$homebrewCleanupExitCode" -eq 1 ]; then
|
||||
printf >&2 '\e[1;31merror: found Homebrew packages not listed in the Brewfile, aborting activation\e[0m\n'
|
||||
printf >&2 '%s\n' "$homebrewCleanupResult"
|
||||
@@ -963,13 +1030,7 @@ in
|
||||
# Homebrew Bundle
|
||||
echo >&2 "Homebrew bundle..."
|
||||
if [ -f "${cfg.prefix}/bin/brew" ]; then
|
||||
PATH="${cfg.prefix}/bin:${lib.makeBinPath [ pkgs.mas ]}:$PATH" \
|
||||
sudo \
|
||||
--preserve-env=PATH \
|
||||
--user=${escapeShellArg cfg.user} \
|
||||
--set-home \
|
||||
env \
|
||||
${cfg.onActivation.brewBundleCmd}
|
||||
${cfg.onActivation.brewBundleCmd { onlyCheck = false; }}
|
||||
else
|
||||
echo -e "\e[1;31merror: Homebrew is not installed, skipping...\e[0m" >&2
|
||||
fi
|
||||
|
||||
@@ -108,10 +108,12 @@
|
||||
./programs/_1password-gui.nix
|
||||
./programs/arqbackup.nix
|
||||
./programs/bash
|
||||
./programs/devenv.nix
|
||||
./programs/direnv.nix
|
||||
./programs/fish.nix
|
||||
./programs/gnupg.nix
|
||||
./programs/man.nix
|
||||
./programs/mas.nix
|
||||
./programs/info
|
||||
./programs/nix-index
|
||||
./programs/ssh.nix
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.programs.devenv;
|
||||
|
||||
hook = shell: "${lib.getExe cfg.package} hook ${shell}";
|
||||
in
|
||||
{
|
||||
meta.maintainers = [
|
||||
lib.maintainers.domenkozar or "domenkozar"
|
||||
lib.maintainers.sandydoo or "sandydoo"
|
||||
lib.maintainers.anish or "anish"
|
||||
];
|
||||
|
||||
options.programs.devenv = {
|
||||
enable = lib.mkEnableOption "devenv, fast, declarative, reproducible and composable developer environments";
|
||||
|
||||
package = lib.mkPackageOption pkgs "devenv" { };
|
||||
|
||||
enableBashIntegration = lib.mkEnableOption "auto-activation of devenv environments in Bash" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
enableFishIntegration = lib.mkEnableOption "auto-activation of devenv environments in Fish" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
enableZshIntegration = lib.mkEnableOption "auto-activation of devenv environments in Zsh" // {
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
programs.bash.interactiveShellInit = lib.mkIf cfg.enableBashIntegration ''
|
||||
eval "$(${hook "bash"})"
|
||||
'';
|
||||
|
||||
programs.fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration ''
|
||||
${hook "fish"} | source
|
||||
'';
|
||||
|
||||
programs.zsh.interactiveShellInit = lib.mkIf cfg.enableZshIntegration ''
|
||||
eval "$(${hook "zsh"})"
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
attrValues
|
||||
concatStringsSep
|
||||
escapeShellArg
|
||||
getExe
|
||||
literalExpression
|
||||
mapAttrsToList
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
mkOptionDefault
|
||||
mkPackageOption
|
||||
optionalString
|
||||
types
|
||||
;
|
||||
|
||||
cfg = config.programs.mas;
|
||||
|
||||
apps = mapAttrsToList (name: id: { inherit name id; }) cfg.packages;
|
||||
|
||||
desiredIds = map (app: toString app.id) apps;
|
||||
homebrewIds = map toString (attrValues config.homebrew.masApps);
|
||||
|
||||
hasWork = cfg.update || cfg.packages != { } || cfg.cleanup || homebrewIds != [ ];
|
||||
|
||||
activationScript =
|
||||
if hasWork then
|
||||
''
|
||||
echo >&2 "setting up App Store apps (mas)..."
|
||||
|
||||
runAsUser() {
|
||||
sudo \
|
||||
--preserve-env=PATH \
|
||||
--set-home \
|
||||
--user=${escapeShellArg cfg.user} \
|
||||
"$@"
|
||||
}
|
||||
|
||||
listStatus=0
|
||||
listOutput=$(
|
||||
runAsUser ${getExe cfg.package} list 2>&1
|
||||
) || listStatus=$?
|
||||
|
||||
if (( listStatus != 0 )); then
|
||||
echo >&2 "warning: mas list failed (exit ''${listStatus}):"
|
||||
echo >&2 "''${listOutput}"
|
||||
if echo "''${listOutput}" | grep -qi "not signed in"; then
|
||||
echo >&2 "login required; skipping App Store installs/updates/cleanup"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Only emit cleanup-only shell variables when cleanup is enabled; otherwise shellcheck
|
||||
# treats them as unused and fails the activation script build.
|
||||
installedIds=()
|
||||
${if cfg.cleanup then
|
||||
''
|
||||
# Parse mas list output: "ID AppName (version)"
|
||||
declare -A installedApps
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
line="''${line#"''${line%%[![:space:]]*}"}"
|
||||
id="''${line%% *}"
|
||||
rest="''${line#"$id"}"
|
||||
rest="''${rest#"''${rest%%[![:space:]]*}"}"
|
||||
name="''${rest% (*}"
|
||||
name="''${name%"''${name##*[![:space:]]}"}"
|
||||
[[ -n "$id" ]] && {
|
||||
installedIds+=( "$id" )
|
||||
installedApps["$id"]="$name"
|
||||
}
|
||||
done <<<"$listOutput"
|
||||
''
|
||||
else
|
||||
''
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
line="''${line#"''${line%%[![:space:]]*}"}"
|
||||
id="''${line%% *}"
|
||||
[[ -n "$id" ]] && installedIds+=( "$id" )
|
||||
done <<<"$listOutput"
|
||||
''}
|
||||
|
||||
${optionalString cfg.update ''
|
||||
runAsUser ${getExe cfg.package} update || true
|
||||
''}
|
||||
|
||||
desiredIds=(
|
||||
${concatStringsSep "\n " desiredIds}
|
||||
)
|
||||
|
||||
is_installed() {
|
||||
local needle=$1
|
||||
for id in "''${installedIds[@]}"; do
|
||||
if [[ "$id" == "$needle" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
${optionalString (cfg.packages != { }) ''
|
||||
for appId in "''${desiredIds[@]}"; do
|
||||
if is_installed "$appId"; then
|
||||
continue
|
||||
fi
|
||||
runAsUser ${getExe cfg.package} install "$appId" || true
|
||||
done
|
||||
''}
|
||||
|
||||
${optionalString cfg.cleanup ''
|
||||
homebrewIds=(
|
||||
${concatStringsSep "\n " homebrewIds}
|
||||
)
|
||||
|
||||
keepIds=( "''${desiredIds[@]}" "''${homebrewIds[@]}" )
|
||||
|
||||
for installedId in "''${installedIds[@]}"; do
|
||||
keep=false
|
||||
for keepId in "''${keepIds[@]}"; do
|
||||
if [[ "$installedId" == "$keepId" ]]; then
|
||||
keep=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $keep; then
|
||||
appName="''${installedApps[$installedId]:-$installedId}"
|
||||
echo >&2 "removing $appName from App Store"
|
||||
runAsUser ${getExe cfg.package} uninstall "$installedId" || true
|
||||
fi
|
||||
done
|
||||
''}
|
||||
''
|
||||
else
|
||||
"";
|
||||
in
|
||||
{
|
||||
options.programs.mas = {
|
||||
enable = mkEnableOption "managing Mac App Store apps with mas";
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = config.system.primaryUser;
|
||||
defaultText = literalExpression "config.system.primaryUser";
|
||||
description = ''
|
||||
The user account that runs {command}`mas`. This user must be signed into the Mac App Store
|
||||
for installs or updates to succeed.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkPackageOption pkgs "mas" { };
|
||||
|
||||
packages = mkOption {
|
||||
type = types.attrsOf types.ints.positive;
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{
|
||||
Xcode = 497799835;
|
||||
"1Password for Safari" = 1569813296;
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Applications to install from the Mac App Store. Attribute names are only for readability;
|
||||
values must be the numeric identifiers used by {command}`mas`.
|
||||
'';
|
||||
};
|
||||
|
||||
update = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to run {command}`mas update` during system activation in addition to installing the
|
||||
configured apps.
|
||||
'';
|
||||
};
|
||||
|
||||
cleanup = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to uninstall Mac App Store apps that are currently installed but not listed in
|
||||
{option}`programs.mas.packages`. Apps listed in {option}`homebrew.masApps` are also preserved.
|
||||
This runs before install/update; any app id not in either set will be removed.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
system.requiresPrimaryUser =
|
||||
mkIf (cfg.enable && options.programs.mas.user.highestPrio == (mkOptionDefault { }).priority)
|
||||
[
|
||||
"programs.mas.enable"
|
||||
];
|
||||
|
||||
environment.systemPackages = mkIf cfg.enable [ cfg.package ];
|
||||
|
||||
system.activationScripts.mas.text = mkIf cfg.enable activationScript;
|
||||
};
|
||||
}
|
||||
@@ -263,7 +263,7 @@ in
|
||||
|
||||
nodeRuntimes = mkOption {
|
||||
type = with types; nonEmptyListOf (enum [ "node20" "node24" ]);
|
||||
default = [ "node20" "node24" ];
|
||||
default = [ "node24" ];
|
||||
description = ''
|
||||
List of Node.js runtimes the runner should support.
|
||||
'';
|
||||
|
||||
@@ -27,6 +27,7 @@ in
|
||||
|
||||
services.spacebar.package = mkOption {
|
||||
type = path;
|
||||
default = pkgs.spacebar;
|
||||
description = "The spacebar package to use.";
|
||||
};
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ in
|
||||
${cfg.activationScripts.keyboard.text}
|
||||
${cfg.activationScripts.fonts.text}
|
||||
${cfg.activationScripts.nvram.text}
|
||||
${cfg.activationScripts.mas.text}
|
||||
${cfg.activationScripts.homebrew.text}
|
||||
|
||||
${cfg.activationScripts.postActivation.text}
|
||||
|
||||
@@ -38,6 +38,18 @@ in
|
||||
description = "Whether to swap the left Command key and left Alt key.";
|
||||
};
|
||||
|
||||
system.keyboard.swapRightCommandAndRightOption = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to swap the right Command key and right Option key.";
|
||||
};
|
||||
|
||||
system.keyboard.swapCapsLockAndEscape = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to swap the Caps Lock key and Escape key.";
|
||||
};
|
||||
|
||||
system.keyboard.swapLeftCtrlAndFn = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
@@ -64,6 +76,14 @@ in
|
||||
(mkIf cfg.remapCapsLockToControl { HIDKeyboardModifierMappingSrc = 30064771129; HIDKeyboardModifierMappingDst = 30064771296; })
|
||||
(mkIf cfg.remapCapsLockToEscape { HIDKeyboardModifierMappingSrc = 30064771129; HIDKeyboardModifierMappingDst = 30064771113; })
|
||||
(mkIf cfg.nonUS.remapTilde { HIDKeyboardModifierMappingSrc = 30064771172; HIDKeyboardModifierMappingDst = 30064771125; })
|
||||
(mkIf cfg.swapCapsLockAndEscape {
|
||||
HIDKeyboardModifierMappingSrc = 30064771129;
|
||||
HIDKeyboardModifierMappingDst = 30064771113;
|
||||
})
|
||||
(mkIf cfg.swapCapsLockAndEscape {
|
||||
HIDKeyboardModifierMappingSrc = 30064771113;
|
||||
HIDKeyboardModifierMappingDst = 30064771129;
|
||||
})
|
||||
(mkIf cfg.swapLeftCommandAndLeftAlt {
|
||||
HIDKeyboardModifierMappingSrc = 30064771299;
|
||||
HIDKeyboardModifierMappingDst = 30064771298;
|
||||
@@ -72,6 +92,14 @@ in
|
||||
HIDKeyboardModifierMappingSrc = 30064771298;
|
||||
HIDKeyboardModifierMappingDst = 30064771299;
|
||||
})
|
||||
(mkIf cfg.swapRightCommandAndRightOption {
|
||||
HIDKeyboardModifierMappingSrc = 30064771303;
|
||||
HIDKeyboardModifierMappingDst = 30064771302;
|
||||
})
|
||||
(mkIf cfg.swapRightCommandAndRightOption {
|
||||
HIDKeyboardModifierMappingSrc = 30064771302;
|
||||
HIDKeyboardModifierMappingDst = 30064771303;
|
||||
})
|
||||
(mkIf cfg.swapLeftCtrlAndFn {
|
||||
HIDKeyboardModifierMappingSrc = 30064771296;
|
||||
HIDKeyboardModifierMappingDst = 1095216660483;
|
||||
|
||||
@@ -3,19 +3,8 @@
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.time;
|
||||
|
||||
timeZone = optionalString (cfg.timeZone != null) ''
|
||||
if ! systemsetup -listtimezones | grep -q "^ ${cfg.timeZone}$"; then
|
||||
echo "${cfg.timeZone} is not a valid timezone. The command 'listtimezones' will show a list of valid time zones." >&2
|
||||
false
|
||||
fi
|
||||
systemsetup -settimezone "${cfg.timeZone}" 2>/dev/null 1>&2
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
@@ -37,8 +26,12 @@ in
|
||||
system.activationScripts.time.text = mkIf (cfg.timeZone != null) ''
|
||||
# Set defaults
|
||||
echo "configuring time..." >&2
|
||||
|
||||
${timeZone}
|
||||
|
||||
if ! systemsetup -listtimezones | grep -q "^ ${cfg.timeZone}$"; then
|
||||
echo "${cfg.timeZone} is not a valid timezone. The command 'listtimezones' will show a list of valid time zones." >&2
|
||||
false
|
||||
fi
|
||||
systemsetup -settimezone "${cfg.timeZone}" 2>/dev/null 1>&2
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
@@ -203,7 +203,9 @@ in
|
||||
else
|
||||
homeDirectory=$(dscl . -read ${dsclUser} NFSHomeDirectory)
|
||||
homeDirectory=''${homeDirectory#NFSHomeDirectory: }
|
||||
if [[ ${escapeShellArg v.home} != "$homeDirectory" ]]; then
|
||||
configuredHomeDirectory=$(realpath ${escapeShellArg v.home})
|
||||
homeDirectory=$(realpath "$homeDirectory")
|
||||
if [[ "$configuredHomeDirectory" != "$homeDirectory" ]]; then
|
||||
printf >&2 '\e[1;31merror: config contains the wrong home directory for %s, aborting activation\e[0m\n' ${name}
|
||||
printf >&2 'nix-darwin does not support changing the home directory of existing users.\n'
|
||||
printf >&2 '\n'
|
||||
|
||||
@@ -21,6 +21,17 @@ _arguments \
|
||||
'(--keep-going -k)'{--keep-going,-k}"[keep going until all builds are finished]"\
|
||||
'(--keep-failed -K)'{--keep-failed,-K}'[keep failed builds (usually in /tmp)]'\
|
||||
'--fallback[If binary download fails, fall back on building from source]'\
|
||||
'--flake[Build from the given flake URI]:flake uri:'\
|
||||
'--no-flake[Disable implicit flake detection]'\
|
||||
'(-L --print-build-logs)'{-L,--print-build-logs}'[Print build logs during the build]'\
|
||||
'--refresh[Refresh flake inputs]'\
|
||||
'--impure[Allow impure evaluation for flakes]'\
|
||||
'--recreate-lock-file[Recreate flake.lock from scratch]'\
|
||||
'*--update-input[Update a specific flake input]:input name:'\
|
||||
'*--override-input[Override a flake input]:input name: :flake url: '\
|
||||
'--offline[Do not access the network]'\
|
||||
'--dry-run[Show what would be done without making changes]'\
|
||||
'-Q[Only print the resulting store path]'\
|
||||
'--show-trace[Print stack trace of evaluation errors]'\
|
||||
'*--option[set Nix configuration option]:options:_nix_options:value:_nix_options_value'\
|
||||
'*--arg[argument to pass to the Nix function]:Name:_nix_complete_function_arg:Value: '\
|
||||
|
||||
@@ -94,7 +94,10 @@ in {
|
||||
tests.nix-enable = makeTest ./tests/nix-enable.nix;
|
||||
tests.nixpkgs-overlays = makeTest ./tests/nixpkgs-overlays.nix;
|
||||
tests.nixpkgs-config-allow-unfree = makeTest ./tests/nixpkgs-config-allow-unfree.nix;
|
||||
tests.programs-devenv = makeTest ./tests/programs-devenv.nix;
|
||||
tests.programs-gnupg = makeTest ./tests/programs-gnupg.nix;
|
||||
tests.programs-mas = makeTest ./tests/programs-mas.nix;
|
||||
tests.programs-mas-no-cleanup = makeTest ./tests/programs-mas-no-cleanup.nix;
|
||||
tests.programs-ssh = makeTest ./tests/programs-ssh.nix;
|
||||
tests.programs-tmux = makeTest ./tests/programs-tmux.nix;
|
||||
tests.programs-zsh = makeTest ./tests/programs-zsh.nix;
|
||||
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
git checkout master
|
||||
git pull
|
||||
|
||||
version=$(jq -r .release version.json)
|
||||
|
||||
# branch off ${version}
|
||||
echo "Cutting nix-darwin-${version} release branch"
|
||||
|
||||
git checkout -b "nix-darwin-${version}"
|
||||
|
||||
sed -i -e "s!- master!- nix-darwin-${version}!" .github/workflows/test.yml
|
||||
sed -i -e "s!NIXPKGS_BRANCH: nixpkgs-unstable!NIXPKGS_BRANCH: nixpkgs-${version}-darwin!" .github/workflows/test.yml
|
||||
|
||||
sed -i -e "s!nixpkgs-unstable!nixpkgs-${version}-darwin!" modules/examples/flake/flake.nix
|
||||
sed -i -e "s!github:nix-darwin/nix-darwin/master!github:nix-darwin/nix-darwin/nix-darwin-${version}!" modules/examples/flake/flake.nix
|
||||
|
||||
sed -i -e "s!nixpkgs/unstable!nixpkgs/stable!g" modules/nix/nixpkgs.nix
|
||||
|
||||
sed -i -e "s!nixpkgs-unstable!nixpkgs-${version}-darwin!" flake.nix
|
||||
nix flake lock
|
||||
|
||||
cat <<EOF > README.md
|
||||
# nix-darwin
|
||||
|
||||
This is the ${version} release branch of nix-darwin. See [the main readme](https://github.com/nix-darwin/nix-darwin#readme) for documentation
|
||||
EOF
|
||||
|
||||
cat <<EOF > version.json
|
||||
{
|
||||
"release": "${version}",
|
||||
"isReleaseBranch": true
|
||||
}
|
||||
EOF
|
||||
|
||||
git add .
|
||||
git commit -m "version: branch off ${version}"
|
||||
|
||||
# update master
|
||||
echo "Updating master to point to next version"
|
||||
|
||||
git checkout master
|
||||
|
||||
## update version.json to point to the next version
|
||||
|
||||
IFS='.' read -r major minor <<< "$version"
|
||||
|
||||
if [[ "$minor" = "11" ]]; then
|
||||
major=$(( major + 1 ))
|
||||
minor="05"
|
||||
else
|
||||
minor="11"
|
||||
fi
|
||||
|
||||
cat <<EOF > version.json
|
||||
{
|
||||
"release": "${major}.${minor}",
|
||||
"isReleaseBranch": false
|
||||
}
|
||||
EOF
|
||||
|
||||
## update readme so that instructions refer to the version we're cutting (our supported stable version)
|
||||
|
||||
sed -i -e "s![0-9][0-9]\.[0-9][0-9]!${version}!g" README.md
|
||||
|
||||
git add .
|
||||
git commit -m "version: bump to ${major}.${minor}"
|
||||
|
||||
nix flake update
|
||||
|
||||
git add .
|
||||
git commit -m "flake.lock: update"
|
||||
@@ -1,5 +1,9 @@
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
brewBundleInstallCmd = config.homebrew.onActivation.brewBundleCmd { onlyCheck = false; };
|
||||
in
|
||||
|
||||
{
|
||||
homebrew.enable = true;
|
||||
homebrew.user = "test-homebrew-user";
|
||||
@@ -7,12 +11,12 @@
|
||||
|
||||
test = ''
|
||||
echo "checking that cleanup check is present in system checks" >&2
|
||||
grep 'brew bundle cleanup --file=' ${config.out}/activate
|
||||
grep "brew bundle --file='.*-Brewfile' cleanup" ${config.out}/activate
|
||||
|
||||
echo "checking that brew bundle command does not have --cleanup flag" >&2
|
||||
if echo "${config.homebrew.onActivation.brewBundleCmd}" | grep -F -- '--cleanup' > /dev/null; then
|
||||
echo "Expected no --cleanup flag in brewBundleCmd"
|
||||
echo "Actual: ${config.homebrew.onActivation.brewBundleCmd}"
|
||||
echo "checking that brew bundle [install] command does not have --cleanup flag" >&2
|
||||
if echo "${brewBundleInstallCmd}" | grep -F -- '--cleanup' > /dev/null; then
|
||||
echo "Expected no --cleanup flag in brewBundleInstallCmd"
|
||||
echo "Actual: ${brewBundleInstallCmd}"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
devenv =
|
||||
pkgs.runCommand "devenv-0.0.0" { } ''
|
||||
mkdir -p $out/bin
|
||||
cat > $out/bin/devenv <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x $out/bin/devenv
|
||||
''
|
||||
// {
|
||||
meta.mainProgram = "devenv";
|
||||
};
|
||||
in
|
||||
{
|
||||
programs.bash.enable = true;
|
||||
programs.zsh.enable = true;
|
||||
programs.fish.enable = true;
|
||||
|
||||
programs.devenv = {
|
||||
enable = true;
|
||||
package = devenv;
|
||||
};
|
||||
|
||||
test = ''
|
||||
echo >&2 "checking devenv bash integration in /etc/bashrc"
|
||||
grep 'devenv hook bash' ${config.out}/etc/bashrc
|
||||
|
||||
echo >&2 "checking devenv zsh integration in /etc/zshrc"
|
||||
grep 'devenv hook zsh' ${config.out}/etc/zshrc
|
||||
|
||||
echo >&2 "checking devenv fish integration in /etc/fish/config.fish"
|
||||
grep 'devenv hook fish' ${config.out}/etc/fish/config.fish
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
mas =
|
||||
pkgs.runCommand "mas-0.0.0" { } ''
|
||||
mkdir -p $out/bin
|
||||
cat > $out/bin/mas <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x $out/bin/mas
|
||||
''
|
||||
// {
|
||||
meta.mainProgram = "mas";
|
||||
};
|
||||
in
|
||||
{
|
||||
system.primaryUser = "primary-mas-user";
|
||||
|
||||
programs.mas = {
|
||||
enable = true;
|
||||
user = "test-mas-user";
|
||||
package = mas;
|
||||
update = true;
|
||||
cleanup = false;
|
||||
packages = {
|
||||
Xcode = 497799835;
|
||||
};
|
||||
};
|
||||
|
||||
homebrew.masApps = {
|
||||
"KeepFromHomebrew" = 424242;
|
||||
};
|
||||
|
||||
test = ''
|
||||
echo "checking mas present in systemPackages" >&2
|
||||
test -x ${config.out}/sw/bin/mas
|
||||
|
||||
echo "checking mas activation script still installs and updates apps" >&2
|
||||
grep 'desiredIds=(' ${config.out}/activate
|
||||
grep '497799835' ${config.out}/activate
|
||||
grep 'mas install \"$appId\"' ${config.out}/activate
|
||||
grep 'mas update' ${config.out}/activate
|
||||
|
||||
echo "checking cleanup-only variables are omitted" >&2
|
||||
if grep 'declare -A installedApps' ${config.out}/activate; then
|
||||
echo "unexpected installedApps declaration when cleanup is disabled" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep 'keepIds=(' ${config.out}/activate; then
|
||||
echo "unexpected keepIds declaration when cleanup is disabled" >&2
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
mas =
|
||||
pkgs.runCommand "mas-0.0.0" { } ''
|
||||
mkdir -p $out/bin
|
||||
cat > $out/bin/mas <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x $out/bin/mas
|
||||
''
|
||||
// {
|
||||
meta.mainProgram = "mas";
|
||||
};
|
||||
in
|
||||
{
|
||||
system.primaryUser = "primary-mas-user";
|
||||
|
||||
programs.mas = {
|
||||
enable = true;
|
||||
user = "test-mas-user";
|
||||
package = mas;
|
||||
update = true;
|
||||
cleanup = true;
|
||||
packages = {
|
||||
"1Password for Safari" = 1569813296;
|
||||
Xcode = 497799835;
|
||||
};
|
||||
};
|
||||
|
||||
homebrew.masApps = {
|
||||
"KeepFromHomebrew" = 424242;
|
||||
};
|
||||
|
||||
test = ''
|
||||
echo "checking mas present in systemPackages" >&2
|
||||
test -x ${config.out}/sw/bin/mas
|
||||
|
||||
echo "checking mas activation script uses requested user" >&2
|
||||
grep -- '--user=test-mas-user' ${config.out}/activate
|
||||
|
||||
echo "checking mas desired ids are present" >&2
|
||||
grep 'desiredIds=(' ${config.out}/activate
|
||||
grep '1569813296' ${config.out}/activate
|
||||
grep '497799835' ${config.out}/activate
|
||||
|
||||
echo "checking mas install loop exists" >&2
|
||||
grep 'mas install \"$appId\"' ${config.out}/activate
|
||||
|
||||
echo "checking mas update is triggered" >&2
|
||||
grep 'mas update' ${config.out}/activate
|
||||
|
||||
echo "checking mas parses installedApps from mas list" >&2
|
||||
grep 'declare -A installedApps' ${config.out}/activate
|
||||
grep 'installedApps\[' ${config.out}/activate
|
||||
|
||||
echo "checking mas cleanup log and uninstall" >&2
|
||||
grep 'removing .* from App Store' ${config.out}/activate
|
||||
grep 'runAsUser .*/mas uninstall' ${config.out}/activate
|
||||
|
||||
echo "checking homebrew.masApps ids are kept during cleanup" >&2
|
||||
grep 'homebrewIds=(' ${config.out}/activate
|
||||
grep '424242' ${config.out}/activate
|
||||
'';
|
||||
}
|
||||
@@ -5,20 +5,28 @@
|
||||
system.keyboard.remapCapsLockToControl = true;
|
||||
system.keyboard.remapCapsLockToEscape = true;
|
||||
system.keyboard.nonUS.remapTilde = true;
|
||||
system.keyboard.swapCapsLockAndEscape = true;
|
||||
system.keyboard.swapLeftCommandAndLeftAlt = true;
|
||||
system.keyboard.swapRightCommandAndRightOption = true;
|
||||
system.keyboard.swapLeftCtrlAndFn = true;
|
||||
|
||||
test = ''
|
||||
echo checking keyboard mappings in /activate >&2
|
||||
grep "hidutil property --set '{\"UserKeyMapping\":.*}'" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingSrc\":30064771129" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingSrc\":30064771113" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingSrc\":30064771172" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771113" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771125" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771129" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771296" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771298" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771299" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771296" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingSrc\":30064771303" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingSrc\":30064771302" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771302" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":30064771303" ${config.out}/activate
|
||||
grep "\"HIDKeyboardModifierMappingDst\":1095216660483" ${config.out}/activate
|
||||
'';
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"release": "26.05",
|
||||
"release": "26.11",
|
||||
"isReleaseBranch": false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user