From bbb1b1e77c9afdd588c5b431e0d028aa8e4e4d68 Mon Sep 17 00:00:00 2001 From: Rezart Qelibari Date: Thu, 31 Mar 2022 10:36:52 +0200 Subject: [PATCH] Add 03-install-brew.sh module --- modules/03-install-brew.sh | 347 +++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100755 modules/03-install-brew.sh diff --git a/modules/03-install-brew.sh b/modules/03-install-brew.sh new file mode 100755 index 0000000..e6a1cb0 --- /dev/null +++ b/modules/03-install-brew.sh @@ -0,0 +1,347 @@ +#!/usr/bin/env zsh + +export HOMEBREW_NO_ANALYTICS_THIS_RUN=1 +export HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT=1 + +function doesUserExist() { + local username=$1 + dscl . -list /Users | grep "^${username}$" 2> /dev/null >&2 +} + +function runAsUser() { + local username=$1 + shift + sudo -Hu "${username}" "${@}" +} + +function runAsHomebrewUser() { + runAsUser ${homebrew_username} "$@" +} + +function ensureUserIsInAdminGroup() { + local username=$1 + dseditgroup -o edit -a "${username}" -t user admin +} + +function ensureUserCannotRunSudo() { + local username=$1 + local sudoersFile="/etc/sudoers.d/disallow-sudo-for-${username}" + [[ -f ${sudoersFile} ]] && return + print -- "Defaults:${username} !authenticate + ${username} ALL=(ALL) !ALL" > "${sudoersFile}" + chown root:wheel "${sudoersFile}" || return 10 + chmod u=rw,g=r,o= "${sudoersFile}" || return 20 +} + +function getFirstFreeRoleAccountID() { + dscl . -list '/Users' UniqueID | grep '_.*' | sort -n -k2 | awk -v i=401 '$2>200 && $2<401 {if(i < $2) { print i; nextfile} else i=$2+1;}' +} + +function createHomebrewUser() { + local username=$1 + local userID=`getFirstFreeRoleAccountID` + sysadminctl -addUser "${username}" -fullName "Homebrew User" -shell /usr/bin/false -home '/var/empty' -roleAccount -UID "${userID}" > /dev/null 2>&1 +} + +function createHomebrewUserIfNeccessary() { + if ! doesUserExist ${homebrew_username}; then + lop -y body:warn -y body -- -i "No Homebrew user named ${homebrew_username} found." -i 'Will create user.' + indicateActivity createHomebrewUser,${homebrew_username} 'Creating Homebrew user' || return 10 + else + lop -y body:note -y body -- -i "Homebrew user named ${homebrew_username} already exists." -i 'Skipping.' + fi +} + +function ensureDirectoryWithDefaultMod() { + local itemPath=${1} + mkdir -p ${itemPath} + ensureHomebrewOwnershipAndPermission ${itemPath} +} + +function ensureHomebrewOwnershipAndPermission() { + local itemPath=${1} + local username=${homebrew_username} + [[ -f ${itemPath} || -d ${itemPath} ]] || return 1 + chown "${username}:admin" ${itemPath} + chmod ug+rwx,o-w ${itemPath} +} + +function ensureInstallPrefix() { + if [ ! -d "${INSTALL_PREFIX}" ]; then + mkdir -p "${INSTALL_PREFIX}" + chown root:wheel "${INSTALL_PREFIX}" + fi +} + +function makeDirsGroupWritableIfExist() { + local dir= + local directories=(bin etc include 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) + + for dir in ${directories}; do + [[ ! -d "${dir}" ]] && continue + ensureHomebrewOwnershipAndPermission ${dir} + done +} + +function ensureZSHDirectories() { + local dir= + local directories=(share/zsh share/zsh/site-functions) + for dir in ${directories}; do + ensureDirectoryWithDefaultMod ${dir} + chmod go-w ${dir} + done +} + +function ensureHomebrewDirectories() { + local dir= + local directories=(bin etc include lib sbin share var opt Homebrew var/homebrew var/homebrew/linked Cellar Caskroom Frameworks) + for dir in ${directories}; do + ensureDirectoryWithDefaultMod ${dir} + done +} + +function ensureHomebrewCacheDirectory() { + ensureDirectoryWithDefaultMod "${homebrew_cache}" + runAsHomebrewUser touch "${homebrew_cache}/.cleaned" +} + +function ensureHomebrewLogDirectory() { + ensureDirectoryWithDefaultMod ${homebrew_log} +} + +function fixInstallPrefixPermissions() { + pushd -q ${dirPath} + makeDirsGroupWritableIfExist + ensureZSHDirectories + ensureHomebrewDirectories + popd -q +} + +function configureInstallPrefix() { + local dirPath=$1 + if [[ -d "${dirPath}" ]]; then + lop -y body -- -d "Install prefix at ${dirPath} already exists. Will correct permissions of possible enclosed folders." + indicateActivity fixInstallPrefixPermissions 'Correct permissions of possible enclosed folders' + else + lop -y body -- -d "Install prefix at ${dirPath} does not exist. Will create it." + indicateActivity createInstallPrefix 'Creating install prefix' + fi +} + +function createInstallPrefix() { + mkdir -p ${dirPath} 2> /dev/null || { + lop -- -e 'Could not create directory' -e $dirPath + return 10 + } + chown root:wheel "${dirPath}" + chmod 744 "${dirPath}" +} + +function downloadHomebrew() { + cd "${homebrew_prefix}/Homebrew" > /dev/null || return 10 + [ -d ".git" ] && return 11 + runAsHomebrewUser git init -q + runAsHomebrewUser git config remote.origin.url "${git_homebrew_remote}" + runAsHomebrewUser git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' + runAsHomebrewUser git config core.autocrlf false + runAsHomebrewUser git config --replace-all homebrew.analyticsmessage false + runAsHomebrewUser git config --replace-all homebrew.caskanalyticsmessage false + runAsHomebrewUser git fetch --quiet --force --depth 1 origin > /dev/null + runAsHomebrewUser git fetch --quiet --force --tags --depth 1 origin > /dev/null + runAsHomebrewUser git reset --hard origin/master +} + +function createBrewCallerScript() { + local username=${homebrew_username} + local brewCallerPath="${homebrew_prefix}/Homebrew/bin/brew_caller" + print -- "#!/usr/bin/env zsh + if [ \"\$(id -un)\" != \"${username}\" ]; then + echo 'brew will be run as ${username} user.' >&2 + sudo -E -u \"${username}\" \"\$0\" \"\$@\" + exit \$? + fi + export HOMEBREW_CACHE=\"${homebrew_cache}\" + export HOMEBREW_LOGS=\"${homebrew_log}\" + export HOMEBREW_CASK_OPTS=\"--no-quarantine \${HOMEBREW_CASK_OPTS}\" + export HOMEBREW_NO_AUTO_UPDATE=1 + export HOMEBREW_NO_ANALYTICS=1 + export HOMEBREW_NO_ANALYTICS_THIS_RUN=1 + export HOMEBREW_NO_ANALYTICS_MESSAGE_OUTPUT=1 + \"${homebrew_prefix}/Homebrew/bin/brew\" \"\$@\"" > ${brewCallerPath} + chown ${username}:admin ${brewCallerPath} + chmod ug+x ${brewCallerPath} + runAsHomebrewUser ln -sf ${homebrew_prefix}/Homebrew/bin/brew_caller "${homebrew_prefix}/bin/brew" +} + +function installHomebrewCore() { + runAsHomebrewUser mkdir -p "${homebrew_prefix}/Homebrew/Library/Taps/homebrew/homebrew-core" || return 10 + pushd -q "${homebrew_prefix}/Homebrew/Library/Taps/homebrew/homebrew-core" + [ -d ".git" ] && return + runAsHomebrewUser git init -q + runAsHomebrewUser git config remote.origin.url "${git_homebrew_core_remote}" + runAsHomebrewUser git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*' + runAsHomebrewUser git config core.autocrlf false + runAsHomebrewUser git fetch --quiet --depth 1 --force origin 'refs/heads/master:refs/remotes/origin/master' > /dev/null + runAsHomebrewUser git remote set-head origin --auto > /dev/null + runAsHomebrewUser git reset --hard origin/master +} + +function createLaunchDaemonsPlist() { + local username=${homebrew_username} + local launcherName="de.astzweig.macos.launchdaemons.$1" + local launcherPath="/Library/LaunchDaemons/${launcherName}.plist" + local brewCommand="$2" + echo " + + + + Label + ${launcherName} + Program + ${homebrew_prefix}/bin/brew + ProgramArguments + + ${brewCommand} + + StartInterval + 1800 + UserName + ${username} + GroupName + admin + +" > "${launcherPath}" + chown root:wheel ${launcherPath} + chmod u=rw,go=r ${launcherPath} +} + +function installHomebrewUpdater() { + createLaunchDaemonsPlist brew-updater update + createLaunchDaemonsPlist brew-upgrader upgrade +} + +function tapHomebrewCask() { + brew tap homebrew/cask ${git_homebrew_cask_remote} >&! /dev/null +} + +function tapHomebrewCaskFonts() { + brew tap homebrew/cask-fonts ${git_homebrew_font_remote} >&! /dev/null +} + +function configure_system() { + createHomebrewUserIfNeccessary || return 10 + indicateActivity ensureUserIsInAdminGroup,${homebrew_username} 'Ensure Homebrew user is in admin group' || return 11 + indicateActivity ensureUserCannotRunSudo,${homebrew_username} 'Ensure Homebrew user can not run sudo' || return 12 + configureInstallPrefix ${homebrew_prefix} || return 13 + ensureHomebrewCacheDirectory || return 14 + ensureHomebrewLogDirectory || return 15 + indicateActivity downloadHomebrew 'Downloading Homebrew' || return 16 + indicateActivity createBrewCallerScript 'Create brew caller script' || return 17 + indicateActivity installHomebrewCore 'Install Homebrew core' || return 18 + indicateActivity installHomebrewUpdater 'Install Homebrew updater' || return 19 + pushd -q / + indicateActivity tapHomebrewCask 'Tapping homebrew/cask' || return 20 + indicateActivity tapHomebrewCaskFonts 'Tapping homebrew/cask-fonts' || return 21 + popd -q +} + +function checkPrerequisites() { + local -A cmds=( + [docopts]='(with -f option supported)' + [dscl]='' + [dseditgroup]='' + [chown]='' + [chmod]='' + [sudo]='' + [grep]='' + [sort]='' + [awk]='' + [sysadminctl]='' + ) + test "`id -u`" -eq 0 || { lop -- -e 'This module requires root access. Please run as root.'; return 11 } + checkCommands +} + +function getDefaultHomebrewUsername() { + print -- _homebrew +} + +function getDefaultHomebrewPrefix() { + print -- /usr/local +} + +function getDefaultHomebrewCachePath() { + print -- /Library/Caches/Homebrew +} + +function getDefaultHomebrewLogPath() { + print -- /var/log/Homebrew +} + +function getDefaultGitHomebrewURL() { + print -- ${HOMEBREW_BREW_GIT_REMOTE:-https://github.com/Homebrew/brew.git} +} + +function getDefaultGitHomebrewCoreURL() { + print -- ${HOMEBREW_BREW_CORE_GIT_REMOTE:-https://github.com/Homebrew/homebrew-core.git} +} + +function getDefaultGitHomebrewCaskURL() { + print -- ${HOMEBREW_BREW_CASK_GIT_REMOTE:-https://github.com/Homebrew/homebrew-cask.git} +} + +function getDefaultGitHomebrewCaskFontsURL() { + print -- ${HOMEBREW_BREW_CASK_FONTS_GIT_REMOTE:-https://github.com/Homebrew/homebrew-cask-fonts.git} +} + +function getQuestions() { + questions=( + 'i: homebrew-username=What shall the Homebrew user'\''s username be? # default:'"$(getDefaultHomebrewUsername)" + 'i: homebrew-prefix=What shall the Homebrew prefix be? # default:'"$(getDefaultHomebrewPrefix)" + 'i: homebrew-cache=What shall the Homebrew cache directory be? # default:'"$(getDefaultHomebrewCachePath)" + 'i: homebrew-log=What shall the Homebrew log directory be? # default:'"$(getDefaultHomebrewLogPath)" + 'i: git-homebrew-remote=Which Git repository shall be used to install Homebrew from? # default:'"$(getDefaultGitHomebrewURL)" + 'i: git-homebrew-core-remote=Which Git repository shall be used to install Homebrew core from? # default:'"$(getDefaultGitHomebrewCoreURL)" + 'i: git-homebrew-cask-remote=Which Git repository shall be used to install Homebrew cask from? # default:'"$(getDefaultGitHomebrewCaskURL)" + 'i: git-homebrew-font-remote=Which Git repository shall be used to install Homebrew cask-fonts from? # default:'"$(getDefaultGitHomebrewCaskFontsURL)" + ) +} + +function getUsage() { + read -r -d '' text <<- USAGE + Usage: + $cmdName show-questions [ ]... + $cmdName [-v] [-d FILE] --homebrew-prefix PREFIX --homebrew-username NAME --homebrew-cache PATH --homebrew-log PATH --git-homebrew-remote URL --git-homebrew-core-remote URL --git-homebrew-cask-remote URL --git-homebrew-font-remote URL + + Create a designated Homebrew user who may not login to the system but is the + only one able to install homebrew software systemwide. Install Homebrew at + given PREFIX and make the new Homebrew user the owner of that. + + Options: + --homebrew-prefix PREFIX Path to folder that shall be the prefix of + the system wide Homebrew installation [default: $(getDefaultHomebrewPrefix)]. + --git-homebrew-remote URL Git URL to the Homebrew repository [default: $(getDefaultGitHomebrewURL)]. + --git-homebrew-core-remote URL Git URL to the Homebrew core repository [default: $(getDefaultGitHomebrewCoreURL)]. + --git-homebrew-cask-remote URL Git URL to the Homebrew cask repository [default: $(getDefaultGitHomebrewCaskURL)]. + --git-homebrew-font-remote URL Git URL to the Homebrew cask-fonts repository [default: $(getDefaultGitHomebrewCaskFontsURL)]. + --homebrew-cache PATH Path to folder that shall be used as the + cache for Homebrew [default: $(getDefaultHomebrewCachePath)]. + --homebrew-log PATH Path to folder that shall be used as the log + directory for Homebrew [default: $(getDefaultHomebrewLogPath)]. + --homebrew-username NAME Username of the designated Homebrew user. + [default: $(getDefaultHomebrewUsername)]. + -d FILE, --logfile FILE Print log message to logfile instead of stdout. + -v, --verbose Be more verbose. + ---- + $cmdName 0.1.0 + Copyright (C) 2022 Rezart Qelibari, Astzweig GmbH & Co. KG + License EUPL-1.2. There is NO WARRANTY, to the extent permitted by law. + USAGE + print ${text} +} + +if [[ "${ZSH_EVAL_CONTEXT}" == toplevel ]]; then + test -f "${ASTZWEIG_MACOS_SYSTEM_LIB}" || { echo 'This module requires macos-system library. Please run again with macos-system library provieded as a path in ASTZWEIG_MACOS_SYSTEM_LIB env variable.'; return 10 } + source "${ASTZWEIG_MACOS_SYSTEM_LIB}" + module_main $0 "$@" +fi