particle-os-modules/modules/brew/brew.sh
Thomas Mecattaf a7e074fe36
feat: Add Brew module (#239)
* feat(brew): add new Brew module for Homebrew installation and management

* feat(brew): add new Brew module for Homebrew installation and management

* feat(brew): part 2 of adding new Brew module for Homebrew installation and management

* feat(brew): part 2.2 of adding new Brew module for Homebrew installation and management

* feat(brew): part 3 of adding new Brew module for Homebrew installation and management

* docs: Add kebab-cases for module entries in README

* docs: Don't add whitespaces in module.yml

* chore: Use arrays for package list & add quotes + brackets to some strings

to make it more safe.

* feat(brew): systemd services and timers written dynamically

* feat(brew): systemd services and timers written dynamically 2

* feat(brew): clean up fish completion file name

* chore: Remove duplicate brew completions file

* feat(brew): tweak in brew.sh 3

* chore: Rename shell completion to completions pt. 1

* chore: Rename shell completion to completions pt. 2

* chore: Rename shell completion to completions pt. 3

* feat(brew): Nofile Limits flag, update brew.sh module.yml and README.md

* feat(brew): Brew analytics opt-out flag

* Brew Analytics Enabled by default in docs

* docs: `build-time` instead of `build time`

* docs: Improve `gcc` package log explanation

* fix(brew): Fetching YAML entries due to miss of transition from `_` to `-`

* chore(brew): Improve disabling analytics write to `environment`

* chore(brew): Add script for modifying nofile limit

* chore(brew): Forgot to source the nofile script

* chore(brew): Disable option of installing packages pt.1

* chore(brew): Disable option of installing packages pt.2

* chore(brew): Disable option of installing packages pt.3

* docs(brew): Remove installing packages as a feature

* chore(brew): Add more logs

* chore(brew): Fix typos in logs

* fix(brew): Use `awk` instead of `grep` which fails for stupid reason

Without any error logs either.

* fix(brew): Update & upgrade permutation

* docs(brew): Fix upgrade & update permutation

* chore(brew): Make small tweaks to brew-analytics

If environment is empty, than don't make the extra whiteline

* docs(brew): Slight order adjustment in README inside "Features"

* docs(brew): Add link to Homebrew

* docs(brew): Add note about uninstalling brew

* docs(brew): Include explanation on why manual uninstallation is necessary

Also give big thanks to M2

* docs(brew): Further explain on why the manual uninstallation is needed

* docs(brew): Add link to files module documentation

* docs(brew): Be more detailed about how the module works

* docs(brew): Some small fixes & restore credits

* docs(brew): Rename Brew to Linuxbrew

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* docs(brew): Change features wording

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* docs(brew): Reword update & upgrade wording

* docs(brew): Move update & upgrade service labels above timers

* docs(brew): Change brew documentation wording

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* docs(brew): Syntax highlighting for bash script

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* docs(brew): Improve uninstallation intro wording

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* docs(brew): Move brew-analytics above warning

* docs(brew): Update update/upgrade ordering pt. 1

* docs(brew): Update update/upgrade naming pt. 2

* docs(brew): Update update/upgrade order pt. 3

* chore(brew): Use `#!/usr/bin/env fish` instead of `#!/usr/bin/fish`

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* chore(brew): Use `#!/usr/bin/env bash` instead of `#!/bin/sh`

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* chore(brew): Set shellcheck from sh to bash

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* fix(brew): Convert SH-ism to bash in bash completion

* chore(brew): Use double brackets in bash shell completion

* chore(brew): Make rc in brackets

* chore(brew): Use more double brackets

* chore(brew): Add more double brackets pt. 2

* chore(brew): Make bash shell completion spacing better

* chore(brew): Revert to `bash-completions` completion

* docs(brew): Add comment about why bash completion uses sh

* fix(brew): Apply brew path clash fix

* docs(brew): Explain brew path clash fix

* docs(brew): Add file location of brew path fix

* docs(brew): Reword exporting brew path

* docs(brew): Reword again

* docs(brew): Reword...

* chore(brew): Copy bash completion to `/usr/etc` instead of `/etc`

* chore(brew): Make `/usr/etc/profile.d` if it doesn't exist

* docs(brew): Fix typo in README

* docs(brew): Use caution card instead of warning string

* docs(brew): Brew path reword

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* chore(brew): Implement code comment fix

Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>

* docs(brew): Try to improve `nofile-limits` section

* docs(brew): Use serverfault link instead

* docs(brew): Thread, not article

* docs(brew): Improve Configuration section

* docs(brew): Than vs then

Love to mess this one

* docs(brew): Uninstall script wording fix

---------

Co-authored-by: fiftydinar <65243233+fiftydinar@users.noreply.github.com>
Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>
2024-06-03 09:25:45 +00:00

259 lines
8.9 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
# Convince the installer that we are in CI
touch /.dockerenv
# Debugging
DEBUG="${DEBUG:-false}"
if [[ "${DEBUG}" == true ]]; then
set -x
fi
# Check if gcc is installed
if ! command -v gcc &> /dev/null
then
echo "ERROR: \"gcc\" package could not be found."
echo " Brew depends on \"gcc\" in order to function"
echo " Please include \"gcc\" in the list of packages to install with the system package manager"
exit 1
fi
# Module-specific directories and paths
MODULE_DIRECTORY="${MODULE_DIRECTORY:-/tmp/modules}"
# Configuration values
AUTO_UPDATE=$(echo "${1}" | yq -I=0 ".auto-update")
if [[ -z "${AUTO_UPDATE}" || "${AUTO_UPDATE}" == "null" ]]; then
AUTO_UPDATE=true
fi
UPDATE_INTERVAL=$(echo "${1}" | yq -I=0 ".update-interval")
if [[ -z "${UPDATE_INTERVAL}" || "${UPDATE_INTERVAL}" == "null" ]]; then
UPDATE_INTERVAL="6h"
fi
UPDATE_WAIT_AFTER_BOOT=$(echo "${1}" | yq -I=0 ".update-wait-after-boot")
if [[ -z "${UPDATE_WAIT_AFTER_BOOT}" || "${UPDATE_WAIT_AFTER_BOOT}" == "null" ]]; then
UPDATE_WAIT_AFTER_BOOT="10min"
fi
AUTO_UPGRADE=$(echo "${1}" | yq -I=0 ".auto-upgrade")
if [[ -z "${AUTO_UPGRADE}" || "${AUTO_UPGRADE}" == "null" ]]; then
AUTO_UPGRADE=true
fi
UPGRADE_INTERVAL=$(echo "$1" | yq -I=0 ".upgrade-interval")
if [[ -z "${UPGRADE_INTERVAL}" || "${UPGRADE_INTERVAL}" == "null" ]]; then
UPGRADE_INTERVAL="8h"
fi
UPGRADE_WAIT_AFTER_BOOT=$(echo "${1}" | yq -I=0 ".upgrade-wait-after-boot")
if [[ -z "${UPGRADE_WAIT_AFTER_BOOT}" || "${UPGRADE_WAIT_AFTER_BOOT}" == "null" ]]; then
UPGRADE_WAIT_AFTER_BOOT="30min"
fi
NOFILE_LIMITS=$(echo "${1}" | yq -I=0 ".nofile-limits")
if [[ -z "${NOFILE_LIMITS}" || "${NOFILE_LIMITS}" == "null" ]]; then
NOFILE_LIMITS=false
fi
BREW_ANALYTICS=$(echo "${1}" | yq -I=0 ".brew-analytics")
if [[ -z "${BREW_ANALYTICS}" || "${BREW_ANALYTICS}" == "null" ]]; then
BREW_ANALYTICS=true
fi
# Create necessary directories
mkdir -p /var/home
mkdir -p /var/roothome
# Always install Brew
echo "Downloading and installing Brew..."
curl -Lo /tmp/brew-install https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh
chmod +x /tmp/brew-install
/tmp/brew-install
# Move Brew installation and set ownership to default user (UID 1000)
tar --zstd -cvf /usr/share/homebrew.tar.zst /home/linuxbrew/.linuxbrew
cp -R /home/linuxbrew /usr/share/homebrew
chown -R 1000:1000 /usr/share/homebrew
# Write systemd service files dynamically
echo "Writing brew-setup service"
cat >/usr/lib/systemd/system/brew-setup.service <<EOF
[Unit]
Description=Setup Brew
Wants=network-online.target
After=network-online.target
ConditionPathExists=!/etc/.linuxbrew
ConditionPathExists=!/var/home/linuxbrew/.linuxbrew
[Service]
Type=oneshot
ExecStart=/usr/bin/mkdir -p /tmp/homebrew
ExecStart=/usr/bin/tar --zstd -xvf /usr/share/homebrew.tar.zst -C /tmp/homebrew
ExecStart=/usr/bin/cp -R -n /tmp/homebrew/home/linuxbrew/.linuxbrew /var/home/linuxbrew
ExecStart=/usr/bin/chown -R 1000:1000 /var/home/linuxbrew
ExecStart=/usr/bin/rm -rf /tmp/homebrew
ExecStart=/usr/bin/touch /etc/.linuxbrew
[Install]
WantedBy=default.target multi-user.target
EOF
echo "Writing brew-update service"
cat >/usr/lib/systemd/system/brew-update.service <<EOF
[Unit]
Description=Auto-update Brew binary
After=local-fs.target
After=network-online.target
ConditionPathIsSymbolicLink=/home/linuxbrew/.linuxbrew/bin/brew
[Service]
User=1000
Type=oneshot
Environment=HOMEBREW_CELLAR=/home/linuxbrew/.linuxbrew/Cellar
Environment=HOMEBREW_PREFIX=/home/linuxbrew/.linuxbrew
Environment=HOMEBREW_REPOSITORY=/home/linuxbrew/.linuxbrew/Homebrew
ExecStart=/usr/bin/bash -c "/home/linuxbrew/.linuxbrew/bin/brew update"
EOF
echo "Writing brew-upgrade service"
cat >/usr/lib/systemd/system/brew-upgrade.service <<EOF
[Unit]
Description=Auto-upgrade Brew packages
After=local-fs.target
After=network-online.target
ConditionPathIsSymbolicLink=/home/linuxbrew/.linuxbrew/bin/brew
[Service]
User=1000
Type=oneshot
Environment=HOMEBREW_CELLAR=/home/linuxbrew/.linuxbrew/Cellar
Environment=HOMEBREW_PREFIX=/home/linuxbrew/.linuxbrew
Environment=HOMEBREW_REPOSITORY=/home/linuxbrew/.linuxbrew/Homebrew
ExecStart=/usr/bin/bash -c "/home/linuxbrew/.linuxbrew/bin/brew upgrade"
EOF
# Write systemd timer files dynamically
echo "Writing brew-update timer"
if [[ -n "${UPDATE_WAIT_AFTER_BOOT}" ]] && [[ "${UPDATE_WAIT_AFTER_BOOT}" != "10min" ]]; then
echo "Applying custom 'wait-after-boot' value in '${UPDATE_WAIT_AFTER_BOOT}' time interval for brew update timer"
fi
if [[ -n "${UPDATE_INTERVAL}" ]] && [[ "${UPDATE_INTERVAL}" != "6h" ]]; then
echo "Applying custom 'update-interval' value in '${UPDATE_INTERVAL}' time interval for brew update timer"
fi
cat >/usr/lib/systemd/system/brew-update.timer <<EOF
[Unit]
Description=Timer for updating Brew binary
Wants=network-online.target
[Timer]
OnBootSec=${UPDATE_WAIT_AFTER_BOOT}
OnUnitInactiveSec=${UPDATE_INTERVAL}
Persistent=true
[Install]
WantedBy=timers.target
EOF
echo "Writing brew-upgrade timer"
if [[ -n "${UPGRADE_WAIT_AFTER_BOOT}" ]] && [[ "${UPGRADE_WAIT_AFTER_BOOT}" != "30min" ]]; then
echo "Applying custom 'wait-after-boot' value in '${UPGRADE_WAIT_AFTER_BOOT}' time interval for brew upgrade timer"
fi
if [[ -n "${UPGRADE_INTERVAL}" ]] && [[ "${UPGRADE_INTERVAL}" != "8h" ]]; then
echo "Applying custom 'upgrade-interval' value in '${UPGRADE_INTERVAL}' time interval for brew upgrade timer"
fi
cat >/usr/lib/systemd/system/brew-upgrade.timer <<EOF
[Unit]
Description=Timer for upgrading Brew packages
Wants=network-online.target
[Timer]
OnBootSec=${UPGRADE_WAIT_AFTER_BOOT}
OnUnitInactiveSec=${UPGRADE_INTERVAL}
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Apply brew shell environment only when shell is interactive
# Fish already includes this fix in brew-fish-completions.sh
# By default Brew applies the shell environment changes globally, which causes path conflicts between system & brew installed programs with same name.
# Universal Blue images include this same fix
if [[ ! -d "/usr/etc/profile.d/" ]]; then
mkdir -p "/usr/etc/profile.d/"
fi
if [[ ! -f "/usr/etc/profile.d/brew.sh" ]]; then
echo "Apply brew path export fix, to solve path conflicts between system & brew programs with same name"
echo "#!/usr/bin/env bash
[[ -d /home/linuxbrew/.linuxbrew && $- == *i* ]] && eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"" > "/usr/etc/profile.d/brew.sh"
fi
# Copy shell configuration files
echo "Copying Brew bash & fish shell completions"
cp -r "${MODULE_DIRECTORY}"/brew/brew-fish-completions.fish /usr/share/fish/vendor_conf.d/brew-fish-completions.fish
cp -r "${MODULE_DIRECTORY}"/brew/brew-bash-completions.sh /usr/etc/profile.d/brew-bash-completions.sh
# Register path symlink
# We do this via tmpfiles.d so that it is created by the live system.
echo "Writing brew tmpfiles.d configuration"
cat >/usr/lib/tmpfiles.d/homebrew.conf <<EOF
d /var/lib/homebrew 0755 1000 1000 - -
d /var/cache/homebrew 0755 1000 1000 - -
d /var/home/linuxbrew 0755 1000 1000 - -
EOF
# Enable the setup service
echo "Enabling brew-setup service"
systemctl enable brew-setup.service
# Always enable or disable update and upgrade services for consistency
if [[ "${AUTO_UPDATE}" == true ]]; then
echo "Enabling auto-updates for Brew binary"
systemctl enable brew-update.timer
else
echo "Disabling auto-updates for Brew binary"
systemctl disable brew-update.timer
fi
if [[ "${AUTO_UPGRADE}" == true ]]; then
echo "Enabling auto-upgrades for Brew packages"
systemctl enable brew-upgrade.timer
else
echo "Disabling auto-upgrades for Brew packages"
systemctl disable brew-upgrade.timer
fi
# Apply nofile limits if enabled
if [[ "${NOFILE_LIMITS}" == true ]]; then
source "${MODULE_DIRECTORY}"/brew/brew-nofile-limits-logic.sh
fi
# Disable homebrew analytics if the flag is set to false
# like secureblue: https://github.com/secureblue/secureblue/blob/live/config/scripts/homebrewanalyticsoptout.sh
if [[ "${BREW_ANALYTICS}" == false ]]; then
if [[ ! -f "/usr/etc/environment" ]]; then
echo "" > "/usr/etc/environment" # touch fails for some reason, probably a bug with it
fi
CURRENT_ENVIRONMENT=$(cat "/usr/etc/environment")
CURRENT_HOMEBREW_CONFIG=$(awk -F= '/HOMEBREW_NO_ANALYTICS/ {print $0}' "/usr/etc/environment")
if [[ -n "${CURRENT_ENVIRONMENT}" ]]; then
if [[ "${CURRENT_HOMEBREW_CONFIG}" == "HOMEBREW_NO_ANALYTICS=0" ]]; then
echo "Disabling Brew analytics"
sed -i 's/HOMEBREW_NO_ANALYTICS=0/HOMEBREW_NO_ANALYTICS=1/' "/usr/etc/environment"
elif [[ -z "${CURRENT_HOMEBREW_CONFIG}" ]]; then
echo "Disabling Brew analytics"
echo "HOMEBREW_NO_ANALYTICS=1" >> "/usr/etc/environment"
elif [[ "${CURRENT_HOMEBREW_CONFIG}" == "HOMEBREW_NO_ANALYTICS=1" ]]; then
echo "Brew analytics are already disabled!"
fi
elif [[ -z "${CURRENT_ENVIRONMENT}" ]]; then
echo "Disabling Brew analytics"
echo "HOMEBREW_NO_ANALYTICS=1" > "/usr/etc/environment"
fi
fi
echo "Brew setup completed"