Initial WIP
This commit is contained in:
@@ -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
@@ -0,0 +1,11 @@
|
||||
.direnv
|
||||
.vscode
|
||||
|
||||
result*
|
||||
|
||||
*.o
|
||||
*.out
|
||||
|
||||
*~
|
||||
*.swp
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user