Initial WIP

This commit is contained in:
Zhaofeng Li
2023-07-01 02:22:36 -06:00
commit 7391ea1ddb
9 changed files with 1027 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
fi
use_flake
+11
View File
@@ -0,0 +1,11 @@
.direnv
.vscode
result*
*.o
*.out
*~
*.swp
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Zhaofeng Li
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+93
View File
@@ -0,0 +1,93 @@
# nix-homebrew (WIP)
`nix-homebrew` manages Homebrew installations on macOS using [nix-darwin](https://github.com/LnL7/nix-darwin).
It pins the Homebrew version and optionally allows for declarative specification of taps.
## Quick Start
First of all, you must have [nix-darwin](https://github.com/LnL7/nix-darwin) configured already.
Add the following to your Flake inputs:
```nix
{
inputs = {
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
# Optional: Declarative tap management
homebrew-core = {
url = "github:homebrew/homebrew-core";
flake = false;
};
homebrew-cask = {
url = "github:homebrew/homebrew-cask";
flake = false;
};
# (...)
};
}
```
Then, Homebrew can be installed with:
```nix
{
output = { self, nix-darwin, nix-homebrew, brew, homebrew-core, homebrew-cask, ... }: {
darwinConfigurations.macbook = {
# (...)
modules = [
{
nix-homebrew = {
# Install Homebrew under the default prefix
enable = true;
# Apple Silicon Only: Also install Homebrew under the default Intel prefix for Rosetta 2
enableRosetta = true;
# User owning the Homebrew prefix
user = "yourname";
# Optional: Declarative tap management
taps = {
"homebrew/homebrew-core" = homebrew-core;
"homebrew/homebrew-cask" = homebrew-cask;
};
# Optional: Enable fully-declarative tap management
#
# With mutableTaps disabled, taps can no longer be added imperatively with `brew tap`.
mutableTaps = false;
};
}
];
};
};
}
```
Once activated, a unified `brew` launcher will be created under `/run/current-system/sw/bin` that automatically selects the correct Homebrew prefix to use based on the architecture.
Run `arch -x86_64 brew` to install X86-64 packages through Rosetta 2.
It's also possible to migrate an existing installation of Homebrew to be managed by `nix-homebrew` (experimental).
You will receive instructions on how to perform the migration during activation.
With `nix-homebrew.mutableTaps = false`, taps can be removed by deleting the corresponding attribute in `nix-homebrew.taps` and activating the new configuration.
## Non-Standard Prefixes
Extra prefixes may be configured:
```nix
{
nix-homebrew.prefixes = {
"/some/prefix" = {
library = "/some/prefix/Library";
taps = {
# ...
};
};
};
}
```
Note that with a non-standard prefix, you will no longer be able to use most bottles (prebuilt packages).
Generated
+78
View File
@@ -0,0 +1,78 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1687709756,
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1688049487,
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+28
View File
@@ -0,0 +1,28 @@
{
description = "Homebrew installation manager for nix-darwin";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
};
outputs = { self, nixpkgs, flake-utils, ... }: let
# System types to support.
supportedSystems = [ "x86_64-darwin" "aarch64-darwin" ];
in flake-utils.lib.eachSystem supportedSystems (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
];
};
}) // {
darwinModules = {
nix-homebrew = ./modules;
};
};
}
+542
View File
@@ -0,0 +1,542 @@
# Homebrew installation manager
#
# By default, we use the same prefixes as upstream for compatbility
# with bottles (prebuilt packages) as well as existing installations
# done by the official script. Adding and updating taps imperatively
# will continue to work - Declarative tap management is optional.
#
# During activation, we create `$HOMEBREW_PREFIX` as well as the
# regular sub-directories (Cellar, Caskroom, etc.) if they don't
# exist already. Where we deviate from upstream is that we synthesize
# `$HOMEBREW_LIBRARY`:
#
# - `Library`:
# - `Taps`: Optionally managed by nix-darwin
# - `Homebrew`: Symlink to Homebrew code inside the Nix store
#
# This avoids the problem in the official layout where state is mixed
# with code inside `$HOMEBREW_REPOSITORY`.
#
# On Apple Silicon, since a separate prefix is required for Intel
# (Rosetta 2), we provide a unified `brew` launcher (`brewLauncher`)
# that automatically selects the correct prefix based on the architecture.
# Use `arch -x86_64 brew` to install X86-64 packages.
{ pkgs, lib, config, ... }:
let
inherit (lib) types;
# When this file exists under $HOMEBREW_PREFIX or a specific
# tap, it means it's managed by us.
nixMarker = ".managed_by_nix_darwin";
cfg = config.nix-homebrew;
# TODO: Maybe don't provide a default at all
defaultBrew = pkgs.fetchFromGitHub {
owner = "homebrew";
repo = "brew";
rev = "eff45ef570f265e226f14ce91da72d7a6e7d516a";
sha256 = "sha256-4hoGm5PUYmz091Pqrii4rpfsLsE7/gjIThHVBq6Vquk=";
};
brew = if cfg.patchBrew then patchBrew cfg.package else cfg.package;
prefixType = types.submodule ({ name, ... }: {
options = {
enable = lib.mkOption {
description = lib.mdDoc ''
Whether to set up this Homebrew prefix.
'';
};
prefix = lib.mkOption {
description = lib.mdDoc ''
The Homebrew prefix.
By default, it's `/opt/homebrew` for Apple Silicon Macs and
`/usr/local` for Intel Macs.
'';
type = types.str;
default = name;
};
library = lib.mkOption {
description = lib.mdDoc ''
The Homebrew library.
By default, it's `/opt/homebrew/Library` for Apple Silicon Macs and
`/usr/local/Homebrew/Library` for Intel Macs.
'';
type = types.str;
};
taps = lib.mkOption {
description = lib.mdDoc ''
A set of Nix-managed taps.
'';
type = types.attrsOf types.package;
default = {};
example = lib.literalExpression ''
{
"homebrew/homebrew-core" = pkgs.fetchFromGitHub {
owner = "homebrew";
repo = "homebrew-core";
rev = "...";
hash = "...";
};
}
'';
};
};
});
# Our unified brew launcher script.
#
# We use `/bin/bash` (Bash 3.2 :/) instead of `${runtimeShell}`
# for compatibility with `arch -x86_64`.
brewLauncher = pkgs.writeScriptBin "brew" (''
#!/bin/bash
set -euo pipefail
cur_arch=$(/usr/bin/uname -m)
'' + lib.optionalString (cfg.prefixes.${cfg.defaultArm64Prefix}.enable) ''
if [[ "$cur_arch" == "arm64" ]]; then
exec "${cfg.prefixes.${cfg.defaultArm64Prefix}.prefix}/bin/brew" "$@"
fi
'' + lib.optionalString (cfg.prefixes.${cfg.defaultIntelPrefix}.enable) ''
if [[ "$cur_arch" == "x86_64" ]]; then
exec "${cfg.prefixes.${cfg.defaultIntelPrefix}.prefix}/bin/brew" "$@"
fi
'' + ''
>&2 echo "nix-homebrew: No Homebrew installation available for $cur_arch"
exit 1
'');
# Prefix-specific bin/brew
#
# No prefix/library/repo auto-detection, everything is configured by Nix.
makeBinBrew = prefix: let
template = pkgs.writeText "brew.in" (''
#!/bin/bash
export HOMEBREW_PREFIX="@prefix@"
export HOMEBREW_LIBRARY="@library@"
export HOMEBREW_REPOSITORY="$HOMEBREW_LIBRARY/.homebrew-is-managed-by-nix"
export HOMEBREW_BREW_FILE="@out@"
# Homebrew itself cannot self-update, so we set
# fake before/after versions to make `update-report.rb` happy
export HOMEBREW_UPDATE_BEFORE="nix"
export HOMEBREW_UPDATE_AFTER="nix"
'' + lib.optionalString (!cfg.mutableTaps) ''
# Disable auto-update since everything is pinned
export HOMEBREW_NO_AUTO_UPDATE=1
'' + lib.optionalString (prefix.taps ? "homebrew/homebrew-core") ''
# Disable API to use pinned homebrew-core
export HOMEBREW_NO_INSTALL_FROM_API=1
'' + ''
# -----
# The following is copied from upstream bin/brew
# Copyright (c) 2009-present, Homebrew contributors
# -----
# Copy and export all HOMEBREW_* variables previously mentioned in
# manpage or used elsewhere by Homebrew.
# These variables are allowed to be set by the user as, e.g., `HOMEBREW_BROWSER`.
MANPAGE_VARS=(
BAT_CONFIG_PATH
BAT_THEME
BROWSER
DISPLAY
EDITOR
NO_COLOR
)
for VAR in "''${MANPAGE_VARS[@]}"
do
# Skip if variable value is empty.
[[ -z "''${!VAR:-}" ]] && continue
VAR_NEW="HOMEBREW_''${VAR}"
# Skip if existing HOMEBREW_* variable is set.
[[ -n "''${!VAR_NEW:-}" ]] && continue
export "''${VAR_NEW}"="''${!VAR}"
done
# We don't want to take the user's value for, e.g., `HOMEBREW_PATH` here!
USED_BY_HOMEBREW_VARS=(
CODESPACES
DBUS_SESSION_BUS_ADDRESS
PATH
TMUX
XDG_RUNTIME_DIR
)
for VAR in "''${USED_BY_HOMEBREW_VARS[@]}"
do
# Skip if variable value is empty.
[[ -z "''${!VAR:-}" ]] && continue
# We unconditionally override `HOMEBREW_*` here.
VAR_NEW="HOMEBREW_''${VAR}"
export "''${VAR_NEW}"="''${!VAR}"
done
unset VAR VAR_NEW MANPAGE_VARS USED_BY_HOMEBREW_VARS
# set from user environment
# shellcheck disable=SC2154
# Use VISUAL if HOMEBREW_EDITOR and EDITOR are unset.
if [[ -z "''${HOMEBREW_EDITOR:-}" && -n "''${VISUAL:-}" ]]
then
export HOMEBREW_EDITOR="''${VISUAL}"
fi
# set from user environment
# shellcheck disable=SC2154
# Set CI variable for Azure Pipelines and Jenkins
# (Set by default on GitHub Actions, Circle and Travis CI)
if [[ -z "''${CI:-}" ]] && [[ -n "''${TF_BUILD:-}" || -n "''${JENKINS_HOME:-}" ]]
then
export CI="1"
fi
# filter the user environment
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
FILTERED_ENV=()
ENV_VAR_NAMES=(
HOME SHELL PATH TERM TERMINFO TERMINFO_DIRS COLUMNS DISPLAY LOGNAME USER CI SSH_AUTH_SOCK SUDO_ASKPASS
http_proxy https_proxy ftp_proxy no_proxy all_proxy HTTPS_PROXY FTP_PROXY ALL_PROXY
)
# Filter all but the specific variables.
for VAR in "''${ENV_VAR_NAMES[@]}" "''${!HOMEBREW_@}"
do
# Skip if variable value is empty.
[[ -z "''${!VAR:-}" ]] && continue
FILTERED_ENV+=("''${VAR}=''${!VAR}")
done
if [[ -n "''${CI:-}" ]]
then
for VAR in "''${!GITHUB_@}"
do
# Skip if variable value is empty.
[[ -z "''${!VAR:-}" ]] && continue
# Skip variables that look like tokens.
[[ "''${VAR}" = *TOKEN* ]] && continue
FILTERED_ENV+=("''${VAR}=''${!VAR}")
done
fi
unset VAR ENV_VAR_NAMES
exec /usr/bin/env -i "''${FILTERED_ENV[@]}" /bin/bash "''${HOMEBREW_LIBRARY}/Homebrew/brew.sh" "$@"
'');
in pkgs.substituteAll {
name = "brew";
src = template;
isExecutable = true;
inherit (prefix) prefix library;
};
setupHomebrew = let
enabledPrefixes = lib.filter (prefix: prefix.enable) (builtins.attrValues cfg.prefixes);
in pkgs.writeShellScript "setup-homebrew" ''
set -euo pipefail
source ${./utils.sh}
NIX_HOMEBREW_UID=$(id -u "${cfg.user}" || (error "Failed to get UID of ${cfg.user}"; exit 1))
NIX_HOMEBREW_GID=$(dscl . -read "/Groups/${cfg.group}" | awk '($1 == "PrimaryGroupID:") { print $2 }' || (error "Failed to get GID of ${cfg.group}"; exit 1))
is_in_nix_store() {
# /nix/store/anything -> inside
# /nix/store/.../link-to-outside-store -> inside
# ./result-link-into-store -> inside
[[ "$1" != "${builtins.storeDir}"* ]] || return 0
if [[ -e "$1" ]]
then
path="$(readlink -f $1)"
else
path="$1"
fi
if [[ "$path" == "${builtins.storeDir}"* ]]
then
return 0
else
return 1
fi
}
is_occupied() {
[[ -e "$1" ]] && ([[ ! -L "$1" ]] || ! is_in_nix_store "$1")
}
${lib.concatMapStrings setupPrefix enabledPrefixes}
if test -n "${toString cfg.enableRosetta}" && ! pgrep -q oahd; then
warn "The Intel Homebrew prefix has been set up, but Rosetta isn't installed yet."
ohai 'Run ''${tty_bold}softwareupdate --install-rosetta''${tty_reset} to install it.'
fi
'';
setupPrefix = prefix: ''
HOMEBREW_PREFIX="${prefix.prefix}"
HOMEBREW_LIBRARY="${prefix.library}"
>&2 echo "setting up Homebrew ($HOMEBREW_PREFIX)..."
HOMEBREW_CODE="$HOMEBREW_LIBRARY/Homebrew"
if is_occupied "$HOMEBREW_CODE"; then
# Probably an existing installation
error "An existing $HOMEBREW_CODE is in the way"
error "$HOMEBREW_PREFIX seems to contain an existing copy of Homebrew."
if [[ -e "$HOMEBREW_PREFIX/.git" ]]; then
# Looks like an Apple Silicon installation
ohai "There are two ways proceed:"
ohai "1. Use the official uninstallation script to remove Homebrew (you will lose all taps and installed packages)"
ohai "2. Delete $HOMEBREW_CODE and clean up other git files later. The existing Homebrew code will be unused."
elif [[ -e "$HOMEBREW_PREFIX/Homebrew/.git" ]]; then
# Looks like an Intel installation
ohai "There are two ways to proceed:"
ohai "1. Use the official uninstallation script to remove Homebrew (you will lose all taps and installed packages)"
ohai "2. Make a backup of $HOMEBREW_LIBRARY/Taps, delete $HOMEBREW_PREFIX/Homebrew, and move the taps back"
else
# Custom installation?
ohai "Please uninstall Homebrew and try activating again."
fi
exit 1
fi
if [[ ! -e "$HOMEBREW_PREFIX/${nixMarker}" ]]; then
initialize_prefix
fi
# Synthetize $HOMEBREW_LIBRARY
/bin/ln -shf "${brew}/Library/Homebrew" "$HOMEBREW_LIBRARY/Homebrew"
${setupTaps prefix.taps}
# Make a fake $HOMEBREW_REPOSITORY
rm -rf "$HOMEBREW_LIBRARY/.homebrew-is-managed-by-nix"
"''${MKDIR[@]}" "$HOMEBREW_LIBRARY/.homebrew-is-managed-by-nix/.git"
"''${CHOWN[@]}" "$NIX_HOMEBREW_UID:$NIX_HOMEBREW_GID" "$HOMEBREW_LIBRARY/.homebrew-is-managed-by-nix"
"''${CHMOD[@]}" 775 "$HOMEBREW_LIBRARY/.homebrew-is-managed-by-nix/"{,.git}
# Link generated bin/brew
BIN_BREW="$HOMEBREW_PREFIX/bin/brew"
if is_occupied "$BIN_BREW"; then
error "An existing $BIN_BREW is in the way"
exit 1
fi
/bin/ln -shf "${makeBinBrew prefix}" "$BIN_BREW"
'';
setupTaps = taps:
# Mixed taps
if cfg.mutableTaps then lib.concatMapStrings (path: let
# Each path must be in the form of `user/repo`
namespace = builtins.head (lib.splitString "/" path);
target = taps.${path};
namespaceDir = "$HOMEBREW_LIBRARY/Taps/${namespace}";
tapDir = "$HOMEBREW_LIBRARY/Taps/${path}";
in ''
if [[ -e "${namespaceDir}" ]] && [[ ! -d "${namespaceDir}" ]]; then
error "$tty_underline${namespaceDir}$tty_reset is in the way and needs to be moved out for $tty_underline${path}$tty_reset"
exit 1
fi
if is_occupied "${tapDir}"; then
error "An existing $tty_underline${tapDir}$tty_reset is in the way"
exit 1
fi
"''${MKDIR[@]}" "${namespaceDir}"
"''${CHOWN[@]}" "$NIX_HOMEBREW_UID:$NIX_HOMEBREW_GID" "${namespaceDir}"
"''${CHMOD[@]}" "ug=rwx" "${namespaceDir}"
/bin/ln -shf "${target}" "${tapDir}"
'') (builtins.attrNames taps)
# Fully declarative taps
else let
env = pkgs.runCommandLocal "taps-env" {} (lib.concatMapStrings (path: let
namespace = builtins.head (lib.splitString "/" path);
target = taps.${path};
in ''
mkdir -p "$out/${namespace}"
ln -s "${target}" "$out/${path}"
'') (builtins.attrNames taps));
in ''
if is_occupied "$HOMEBREW_LIBRARY/Taps"; then
error "An existing $tty_underline$HOMEBREW_LIBRARY/Taps$tty_reset is in the way"
exit 1
fi
/bin/ln -shf "${env}" "$HOMEBREW_LIBRARY/Taps"
'';
# Patch Homebrew to disable self-update behavior
patchBrew = brew: pkgs.runCommandLocal "${brew.name}-patched" {} ''
cp -r "${brew}" "$out"
chmod u+w "$out" "$out/Library/Homebrew/cmd"
substituteInPlace "$out/Library/Homebrew/cmd/update.sh" \
--replace 'for DIR in "''${HOMEBREW_REPOSITORY}"' "for DIR in "
'';
in {
options = {
nix-homebrew = {
enable = lib.mkOption {
description = lib.mdDoc ''
Whether to install Homebrew.
'';
type = types.bool;
default = false;
};
enableRosetta = lib.mkOption {
description = ''
Whether to set up the Homebrew prefix for Rosetta 2.
This is only supported on Apple Silicon Macs.
'';
type = types.bool;
default = false;
};
package = lib.mkOption {
description = lib.mdDoc ''
The homebrew package itself.
'';
type = types.package;
default = defaultBrew;
defaultText = lib.literalExpression "(default Homebrew package)";
};
taps = lib.mkOption {
description = lib.mdDoc ''
A set of Nix-managed taps.
These are applied to the default prefixes.
'';
type = types.attrsOf types.package;
default = {};
example = lib.literalExpression ''
{
"homebrew/homebrew-core" = pkgs.fetchFromGitHub {
owner = "homebrew";
repo = "homebrew-core";
rev = "...";
hash = "...";
};
}
'';
};
mutableTaps = lib.mkOption {
description = lib.mdDoc ''
Whether to allow imperative management of taps.
When enabled, taps can be managed via `brew tap` and
`brew update`.
When disabled, the auto-update functionality is also
automatically disabled with `HOMEBREW_NO_AUTO_UPDATE=1`.
'';
type = types.bool;
default = true;
};
user = lib.mkOption {
description = lib.mdDoc ''
The user owning the Homebrew directories.
'';
type = types.str;
};
group = lib.mkOption {
description = lib.mdDoc ''
The group owning the Homebrew directories.
'';
type = types.str;
default = "admin";
};
# Advanced options
prefixes = lib.mkOption {
description = ''
A set of Homebrew prefixes to set up.
Usually you don't need to configure this and sensible
defaults are already set up.
'';
type = types.attrsOf prefixType;
};
defaultArm64Prefix = lib.mkOption {
description = lib.mdDoc ''
Key of the default Homebrew prefix for ARM64 macOS.
'';
internal = true;
type = types.str;
default = "/opt/homebrew";
};
defaultIntelPrefix = lib.mkOption {
description = lib.mdDoc ''
Key of the default Homebrew prefix for Intel macOS or Rosetta 2.
'';
internal = true;
type = types.str;
default = "/usr/local";
};
extraEnv = lib.mkOption {
description = lib.mdDoc ''
Extra environment variables to set for Homebrew.
'';
type = types.attrsOf types.str;
default = {};
example = lib.literalExpression ''
{
HOMEBREW_NO_ANALYTICS = "1";
}
'';
};
patchBrew = lib.mkOption {
description = ''
Whether to attempt to patch Homebrew to suppress self-updating.
'';
type = types.bool;
default = true;
};
};
};
config = lib.mkIf (cfg.enable) {
assertions = [
{
assertion = cfg.enableRosetta -> pkgs.stdenv.hostPlatform.isAarch64;
message = "nix-homebrew.enableRosetta is set to true but this isn't an Apple Silicon Mac";
}
];
nix-homebrew = {
prefixes = {
"/opt/homebrew" = {
enable = pkgs.stdenv.hostPlatform.isAarch64;
library = "/opt/homebrew/Library";
taps = cfg.taps;
};
"/usr/local" = {
enable = pkgs.stdenv.hostPlatform.isx86_64 || cfg.enableRosetta;
library = "/usr/local/Homebrew/Library";
taps = cfg.taps;
};
};
};
environment.systemPackages = [ brewLauncher ];
system.activationScripts = {
# We set up a new system activation step that sets up Homebrew
preActivation.text = lib.mkAfter ''
${config.system.activationScripts.setup-homebrew.text}
'';
setup-homebrew.text = ''
${setupHomebrew}
'';
};
};
}
+235
View File
@@ -0,0 +1,235 @@
# Snippets from the Homebrew install script.
#
# BSD 2-Clause License
# Copyright (c) 2009-present, Homebrew contributors
# All rights reserved.
#
# Uses:
#
# - HOMEBREW_PREFIX
# - HOMEBREW_LIBRARY
# - NIX_HOMEBREW_UID
# - NIX_HOMEBREW_GID
# TODO: Use Nix-provided utils
# GNU stat behaves differently
STAT_PRINTF=("/usr/bin/stat" "-f")
PERMISSION_FORMAT="%A"
CHMOD=("/bin/chmod")
CHOWN=("/usr/sbin/chown")
CHGRP=("/usr/bin/chgrp")
MKDIR=("/bin/mkdir" "-p")
TOUCH=("/usr/bin/touch")
INSTALL=("/usr/bin/install" -d -o "root" -g "wheel" -m "0755")
# string formatters
if [[ -t 1 ]]
then
tty_escape() { printf "\033[%sm" "$1"; }
else
tty_escape() { :; }
fi
tty_mkbold() { tty_escape "1;$1"; }
tty_underline="$(tty_escape "4;39")"
tty_blue="$(tty_mkbold 34)"
tty_red="$(tty_mkbold 31)"
tty_bold="$(tty_mkbold 39)"
tty_reset="$(tty_escape 0)"
shell_join() {
local arg
printf "%s" "$1"
shift
for arg in "$@"
do
printf " "
printf "%s" "${arg// /\ }"
done
}
chomp() {
printf "%s" "${1/"$'\n'"/}"
}
ohai() {
printf "${tty_blue}==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")"
}
warn() {
printf "${tty_red}Warning${tty_reset}: %s\n" "$(chomp "$1")" >&2
}
error() {
printf "${tty_red}Error${tty_reset}: %s\n" "$(chomp "$1")" >&2
}
get_permission() {
"${STAT_PRINTF[@]}" "${PERMISSION_FORMAT}" "$1"
}
user_only_chmod() {
[[ -d "$1" ]] && [[ "$(get_permission "$1")" != 75[0145] ]]
}
exists_but_not_writable() {
[[ -e "$1" ]] && ! [[ -r "$1" && -w "$1" && -x "$1" ]]
}
user_only_chmod() {
[[ -d "$1" ]] && [[ "$(get_permission "$1")" != 75[0145] ]]
}
get_owner() {
"${STAT_PRINTF[@]}" "%u" "$1"
}
file_not_owned() {
[[ "$(get_owner "$1")" != "${NIX_HOMEBREW_UID}" ]]
}
get_group() {
"${STAT_PRINTF[@]}" "%g" "$1"
}
file_not_grpowned() {
[[ " ${NIX_HOMEBREW_GID} " != *" $(get_group "$1") "* ]]
}
# HOMEBREW_PREFIX
initialize_prefix() {
# Keep relatively in sync with
# https://github.com/Homebrew/brew/blob/master/Library/Homebrew/keg.rb
directories=(
bin etc include lib sbin share opt var
Frameworks
etc/bash_completion.d lib/pkgconfig
share/aclocal share/doc share/info share/locale share/man
share/man/man1 share/man/man2 share/man/man3 share/man/man4
share/man/man5 share/man/man6 share/man/man7 share/man/man8
var/log var/homebrew var/homebrew/linked
bin/brew
)
group_chmods=()
for dir in "${directories[@]}"
do
if exists_but_not_writable "${HOMEBREW_PREFIX}/${dir}"
then
group_chmods+=("${HOMEBREW_PREFIX}/${dir}")
fi
done
# zsh refuses to read from these directories if group writable
directories=(share/zsh share/zsh/site-functions)
zsh_dirs=()
for dir in "${directories[@]}"
do
zsh_dirs+=("${HOMEBREW_PREFIX}/${dir}")
done
directories=(
bin etc include lib sbin share var opt
share/zsh share/zsh/site-functions
var/homebrew var/homebrew/linked
Cellar Caskroom Frameworks
)
mkdirs=()
for dir in "${directories[@]}"
do
if ! [[ -d "${HOMEBREW_PREFIX}/${dir}" ]]
then
mkdirs+=("${HOMEBREW_PREFIX}/${dir}")
fi
done
user_chmods=()
mkdirs_user_only=()
if [[ "${#zsh_dirs[@]}" -gt 0 ]]
then
for dir in "${zsh_dirs[@]}"
do
if [[ ! -d "${dir}" ]]
then
mkdirs_user_only+=("${dir}")
elif user_only_chmod "${dir}"
then
user_chmods+=("${dir}")
fi
done
fi
chmods=()
if [[ "${#group_chmods[@]}" -gt 0 ]]
then
chmods+=("${group_chmods[@]}")
fi
if [[ "${#user_chmods[@]}" -gt 0 ]]
then
chmods+=("${user_chmods[@]}")
fi
chowns=()
chgrps=()
if [[ "${#chmods[@]}" -gt 0 ]]
then
for dir in "${chmods[@]}"
do
if file_not_owned "${dir}"
then
chowns+=("${dir}")
fi
if file_not_grpowned "${dir}"
then
chgrps+=("${dir}")
fi
done
fi
if [[ -d "${HOMEBREW_PREFIX}" ]]
then
if [[ "${#chmods[@]}" -gt 0 ]]
then
"${CHMOD[@]}" "u+rwx" "${chmods[@]}"
fi
if [[ "${#group_chmods[@]}" -gt 0 ]]
then
"${CHMOD[@]}" "g+rwx" "${group_chmods[@]}"
fi
if [[ "${#user_chmods[@]}" -gt 0 ]]
then
"${CHMOD[@]}" "go-w" "${user_chmods[@]}"
fi
if [[ "${#chowns[@]}" -gt 0 ]]
then
"${CHOWN[@]}" "${NIX_HOMEBREW_UID}" "${chowns[@]}"
fi
if [[ "${#chgrps[@]}" -gt 0 ]]
then
"${CHGRP[@]}" "${NIX_HOMEBREW_GID}" "${chgrps[@]}"
fi
else
"${INSTALL[@]}" "${HOMEBREW_PREFIX}"
fi
if [[ "${#mkdirs[@]}" -gt 0 ]]
then
"${MKDIR[@]}" "${mkdirs[@]}"
"${CHMOD[@]}" "ug=rwx" "${mkdirs[@]}"
if [[ "${#mkdirs_user_only[@]}" -gt 0 ]]
then
"${CHMOD[@]}" "go-w" "${mkdirs_user_only[@]}"
fi
"${CHOWN[@]}" "${NIX_HOMEBREW_UID}" "${mkdirs[@]}"
"${CHGRP[@]}" "${NIX_HOMEBREW_GID}" "${mkdirs[@]}"
fi
if ! [[ -d "${HOMEBREW_LIBRARY}" ]]
then
"${MKDIR[@]}" "${HOMEBREW_LIBRARY}"
fi
"${CHOWN[@]}" "-R" "${NIX_HOMEBREW_UID}:${NIX_HOMEBREW_GID}" "${HOMEBREW_LIBRARY}"
"${TOUCH[@]}" "${HOMEBREW_PREFIX}/.managed_by_nix_darwin"
}
# vim: set et ts=2 sw=2:
+14
View File
@@ -0,0 +1,14 @@
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
flake-compat = builtins.fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
};
flake = import flake-compat {
src = ./.;
};
shell = flake.shellNix.default;
in shell