209 lines
5.8 KiB
Nix
209 lines
5.8 KiB
Nix
{
|
|
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;
|
|
};
|
|
}
|