Add auto-migration support
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: macos-13
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Test nuke-homebrew-repository
|
||||
run: |
|
||||
nix build .#nuke-homebrew-repository.passthru.tests.test-nuke -L
|
||||
|
||||
- name: Install some package with Homebrew
|
||||
run: |
|
||||
brew install unbound
|
||||
|
||||
- name: Add a third-party tap imperatively
|
||||
run: |
|
||||
brew tap koekeishiya/formulae
|
||||
|
||||
- name: Set up /run for nix-darwin
|
||||
run: |
|
||||
printf 'run\tprivate/var/run\n' | sudo tee -a /etc/synthetic.conf
|
||||
sudo /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t || true
|
||||
|
||||
- name: Migrate existing Homebrew installation (imperative taps)
|
||||
run: |
|
||||
sudo rm /etc/bashrc
|
||||
./ci/activate-example.sh migrate
|
||||
|
||||
- name: Check that we can still use the unbound package
|
||||
run: $(brew --prefix)/sbin/unbound -V
|
||||
|
||||
- name: Check that we can still use the tap that we added imperatively
|
||||
run: brew install koekeishiya/formulae/yabai
|
||||
|
||||
#- name: Migrate to fully-declarative tap management
|
||||
# run: |
|
||||
# false
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DIR=$(dirname $0)
|
||||
|
||||
if [[ "$#" != "1" ]]; then
|
||||
>&2 echo "Usage: $0 [example]"
|
||||
exit 1
|
||||
fi
|
||||
example="$1"
|
||||
|
||||
nix-instantiate --eval --json -E 'builtins.currentSystem' >"${DIR}/system.json"
|
||||
systemProfile=$(nix build "./${DIR}/..#darwinConfigurations.ci.${example}.system" -L --no-link --print-out-paths)
|
||||
|
||||
>&2 echo "Built $systemProfile"
|
||||
|
||||
sudo "$systemProfile/activate"
|
||||
"$systemProfile/activate-user"
|
||||
|
||||
# vim: set et ts=2 sw=2:
|
||||
@@ -0,0 +1,25 @@
|
||||
# Activates example configurations on CI
|
||||
|
||||
{ self, nix-darwin, nixpkgs, ... }:
|
||||
|
||||
let
|
||||
system = builtins.fromJSON (builtins.readFile ./system.json);
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
inherit (pkgs) lib;
|
||||
makeProfile = example: nix-darwin.lib.darwinSystem {
|
||||
inherit system pkgs;
|
||||
modules = [
|
||||
self.darwinModules.nix-homebrew
|
||||
(../examples + "/${example}.nix")
|
||||
{
|
||||
documentation.enable = false;
|
||||
services.nix-daemon.enable = true;
|
||||
nix-homebrew = {
|
||||
user = lib.mkForce "runner";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
in {
|
||||
migrate = makeProfile "migrate";
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
migrate
|
||||
@@ -0,0 +1 @@
|
||||
"migrate"
|
||||
@@ -0,0 +1 @@
|
||||
"aarch64-darwin"
|
||||
@@ -0,0 +1,10 @@
|
||||
# Migrate from an existing Homebrew installation
|
||||
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
nix-homebrew = {
|
||||
enable = true;
|
||||
autoMigrate = true;
|
||||
user = "yourname";
|
||||
};
|
||||
}
|
||||
Generated
+43
-10
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"brew-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"lastModified": 1687876440,
|
||||
"narHash": "sha256-4hoGm5PUYmz091Pqrii4rpfsLsE7/gjIThHVBq6Vquk=",
|
||||
"owner": "Homebrew",
|
||||
"repo": "brew",
|
||||
"rev": "eff45ef570f265e226f14ce91da72d7a6e7d516a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"owner": "Homebrew",
|
||||
"ref": "4.0.26",
|
||||
"repo": "brew",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -34,7 +35,38 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688307440,
|
||||
"narHash": "sha256-7PTjbN+/+b799YN7Tk2SS5Vh8A0L3gBo8hmB7Y0VXug=",
|
||||
"owner": "LnL7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "b06bab83bdf285ea0ae3c8e145a081eb95959047",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "LnL7",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1687274257,
|
||||
"narHash": "sha256-TutzPriQcZ8FghDhEolnHcYU2oHIG5XWF+/SUBNnAOE=",
|
||||
"path": "/nix/store/22qgs3skscd9bmrxv9xv4q5d4wwm5ppx-source",
|
||||
"rev": "2c9ecd1f0400076a4d6b2193ad468ff0a7e7fdc5",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1688049487,
|
||||
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
|
||||
@@ -52,9 +84,10 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"brew-src": "brew-src",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
||||
@@ -3,19 +3,23 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nix-darwin.url = "github:LnL7/nix-darwin";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
flake-compat = {
|
||||
url = "github:edolstra/flake-compat";
|
||||
brew-src = {
|
||||
url = "github:Homebrew/brew/4.0.26";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, ... }: let
|
||||
outputs = { self, nixpkgs, nix-darwin, flake-utils, ... } @ inputs: let
|
||||
# System types to support.
|
||||
supportedSystems = [ "x86_64-darwin" "aarch64-darwin" ];
|
||||
in flake-utils.lib.eachSystem supportedSystems (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
packages = pkgs.callPackage ./pkgs {
|
||||
inherit inputs;
|
||||
};
|
||||
devShell = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
];
|
||||
@@ -24,5 +28,8 @@
|
||||
darwinModules = {
|
||||
nix-homebrew = ./modules;
|
||||
};
|
||||
darwinConfigurations = {
|
||||
ci = import ./ci inputs;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
+30
-9
@@ -32,6 +32,8 @@ let
|
||||
|
||||
cfg = config.nix-homebrew;
|
||||
|
||||
tools = pkgs.callPackage ../pkgs { };
|
||||
|
||||
# TODO: Maybe don't provide a default at all
|
||||
defaultBrew = pkgs.fetchFromGitHub {
|
||||
owner = "homebrew";
|
||||
@@ -289,25 +291,34 @@ let
|
||||
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."
|
||||
warn "An existing $HOMEBREW_CODE is in the way"
|
||||
warn "$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."
|
||||
ohai "Looks like an Apple Silicon installation (Homebrew prefix is the repository)"
|
||||
HOMEBREW_REPOSITORY="$HOMEBREW_PREFIX"
|
||||
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"
|
||||
ohai "Looks like an Intel installation (Homebrew repository is under the 'Homebrew' subdirectory)"
|
||||
HOMEBREW_REPOSITORY="$HOMEBREW_PREFIX/Homebrew"
|
||||
else
|
||||
# Custom installation?
|
||||
ohai "Please uninstall Homebrew and try activating again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 1
|
||||
if [[ -z "${toString cfg.autoMigrate}" ]]; then
|
||||
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. Set nix-homebrew.autoMigrate = true; to allow nix-homebrew to migrate the installation"
|
||||
|
||||
ohai "During auto-migration, nix-homebrew will delete the existing installation while keeping installed packages."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ohai "Attempting to migrate Homebrew installation..."
|
||||
${tools.nuke-homebrew-repository} "$HOMEBREW_REPOSITORY"
|
||||
fi
|
||||
|
||||
if [[ ! -e "$HOMEBREW_PREFIX/${nixMarker}" ]]; then
|
||||
@@ -442,6 +453,16 @@ in {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
autoMigrate = lib.mkOption {
|
||||
description = ''
|
||||
Whether to allow nix-homebrew to automatically migrate existing Homebrew installations.
|
||||
|
||||
When enabled, the activation script will automatically delete
|
||||
Homebrew repositories while keeping installed packages.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
user = lib.mkOption {
|
||||
description = lib.mdDoc ''
|
||||
The user owning the Homebrew directories.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{ callPackage, inputs ? {} }:
|
||||
{
|
||||
nuke-homebrew-repository = callPackage ./nuke-homebrew-repository {
|
||||
inherit inputs;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{ lib
|
||||
, pkgs
|
||||
, substituteAll
|
||||
, runtimeShell
|
||||
, coreutils
|
||||
, findutils
|
||||
, gnugrep
|
||||
, gnused
|
||||
, gitMinimal
|
||||
|
||||
, inputs
|
||||
, runCommandLocal
|
||||
}:
|
||||
|
||||
let
|
||||
script = substituteAll {
|
||||
name = "nuke-homebrew-repository";
|
||||
src = ./nuke-homebrew-repository.sh.in;
|
||||
isExecutable = true;
|
||||
|
||||
inherit runtimeShell;
|
||||
path = lib.makeBinPath [ coreutils findutils gnugrep gnused gitMinimal ];
|
||||
};
|
||||
|
||||
brew-src = inputs.brew-src or (throw "The tests can only be run with flakes");
|
||||
|
||||
test-nuke = runCommandLocal "test-nuke" {
|
||||
nativeBuildInputs = [ gitMinimal ];
|
||||
} ''
|
||||
must_exist() {
|
||||
if [[ ! -e "$1" ]]; then
|
||||
>&2 echo "Error: $1 should have been kept"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
must_not_exist() {
|
||||
if [[ -e "$1" ]]; then
|
||||
>&2 echo "Error: $1 should have been deleted"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Set up test Homebrew repo
|
||||
cp -R ${brew-src}/ brew
|
||||
chmod -R u+w brew
|
||||
pushd brew >/dev/null
|
||||
git init -q
|
||||
git add -f -A
|
||||
git -c "user.name=CI" -c "user.email=ci@invalid.tld" commit -qm "Test"
|
||||
popd >/dev/null
|
||||
|
||||
# Create a fake tap
|
||||
mkdir -p brew/Library/Taps/apple/homebrew-apple
|
||||
touch brew/Library/Taps/apple/homebrew-apple/content
|
||||
|
||||
# Create a fake package
|
||||
mkdir -p brew/Library/Cellar/some-package/1.0.0
|
||||
touch brew/Library/Cellar/some-package/1.0.0/content
|
||||
touch brew/bin/some-command
|
||||
|
||||
${script} brew
|
||||
|
||||
must_not_exist "brew/README.md"
|
||||
must_not_exist "brew/Library/Homebrew"
|
||||
|
||||
must_exist "brew/Library/Taps/apple/homebrew-apple/content"
|
||||
must_exist "brew/Library/Cellar/some-package/1.0.0/content"
|
||||
must_exist "brew/bin/some-command"
|
||||
|
||||
>>$out echo "Success"
|
||||
'';
|
||||
in script // {
|
||||
passthru.tests = {
|
||||
inherit test-nuke;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#!@runtimeShell@
|
||||
set -euo pipefail
|
||||
|
||||
# Note that we do rely on GNU-ism here
|
||||
export PATH="@path@"
|
||||
|
||||
# Homebrew keeps state (Library/Taps, bin, etc, ...) as ignored
|
||||
# files in `$HOMEBREW_REPOSITORY`. We want to uninstall Homebrew
|
||||
# while keeping all state, so we remove all files tracked by git.
|
||||
|
||||
if [[ "$#" != "1" ]]; then
|
||||
>&2 echo "Usage: $0 <homebrew repository>"
|
||||
>&2 echo "This script tries to nuke all tracked files in a Homebrew repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pushd "$1" >/dev/null
|
||||
|
||||
temp_gitconfig=$(mktemp gitconfig.XXXXXXXXXX)
|
||||
echo -e "[safe]\ndirectory = $(readlink -f $1)" >"${temp_gitconfig}"
|
||||
export GIT_CONFIG_GLOBAL="${temp_gitconfig}"
|
||||
|
||||
finish() {
|
||||
rm -f "${temp_gitconfig}"
|
||||
}
|
||||
trap finish EXIT
|
||||
|
||||
if [[ ! -e ".git" ]]; then
|
||||
>&2 echo "${PWD} is not a git repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -E "^# Homebrew" "README.md" >/dev/null; then
|
||||
>&2 echo "${PWD} does not looks like a Homebrew checkout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# First, delete all files
|
||||
files_to_delete=()
|
||||
while read -r file; do
|
||||
if [[ -e "$file" ]] || [[ -L "$file" ]]; then
|
||||
files_to_delete+=("$file")
|
||||
fi
|
||||
done < <(git ls-files)
|
||||
|
||||
printf "%s\0" "${files_to_delete[@]}" | xargs -0 rm
|
||||
|
||||
# Next, delete all directories
|
||||
while read -r dir; do
|
||||
if [[ -d "$dir" ]]; then
|
||||
rmdir -p --ignore-fail-on-non-empty "$dir"
|
||||
fi
|
||||
done < <(git ls-files | sed 's,/[^/]*$,,' | sort | uniq)
|
||||
|
||||
# Finally, delete .git
|
||||
rm -rf --one-file-system ".git"
|
||||
|
||||
# vim: set et ts=2 sw=2:
|
||||
@@ -1,14 +0,0 @@
|
||||
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