install.fairie/home/.chezmoiscripts/universal/run_before_05-system.sh.tmpl
2024-05-28 06:55:42 +00:00

860 lines
37 KiB
Bash

#!/usr/bin/env bash
# @file System Tweaks
# @brief Applies a set of system tweaks such as ensuring the hostname is set, setting the timezone, and more
# @description
# This script applies system tweaks that should be made before the rest of the provisioning process.
{{ includeTemplate "universal/profile-before" }}
{{ includeTemplate "universal/logg-before" }}
export VOLTA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/volta"
export PATH="$VOLTA_HOME/bin:$PATH"
# @description
# This script determines the ideal `/swapfile` size by checking how much RAM is available on the system.
# It then creates the appropriate `/swapfile` by considering factors such as the file system type. It
# currently supports BTRFS and regular file systems.
#
# After the `/swapfile` is created, it is enabled and assigned the appropriate permissions.
#
# #### TODO
#
# * Add logic that creates a swapfile for ZFS-based systems
# * Integrate logic from https://gitlab.com/megabyte-labs/gas-station/-/blob/master/roles/system/common/tasks/linux/swap.yml
allocateSwap() {
if [ "{{ .host.distro.family }}" = "linux" ]; then
if [ ! -f /swapfile ]; then
### Determine ideal size of /swapfile
MEMORY_IN_KB="$(grep MemTotal /proc/meminfo | sed 's/.* \(.*\) kB/\1/')"
MEMORY_IN_GB="$((MEMORY_IN_KB / 1024 / 1024))"
if [ "$MEMORY_IN_GB" -gt 64 ]; then
SWAP_SPACE="$((MEMORY_IN_GB / 10))"
elif [ "$MEMORY_IN_GB" -gt 32 ]; then
SWAP_SPACE="$((MEMORY_IN_GB / 8))"
elif [ "$MEMORY_IN_GB" -gt 8 ]; then
SWAP_SPACE="$((MEMORY_IN_GB / 4))"
else
SWAP_SPACE="$MEMORY_IN_GB"
fi
### Create /swapfile
FS_TYPE="$(df -Th | grep ' /$' | sed 's/[^ ]*\s*\([^ ]*\).*/\1/')"
if [ "$FS_TYPE" == 'btrfs' ]; then
gum log -sl info 'Creating BTRFS /swapfile'
sudo btrfs filesystem mkswapfile /swapfile
elif [ "$FS_TYPE" == 'zfs' ]; then
gum log -sl warn 'ZFS system detected - add logic here to add /swapfile'
else
gum log -sl info "Creating a $SWAP_SPACE GB /swapfile"
sudo fallocate -l "${SWAP_SPACE}G" /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
fi
### Enable the /swapfile
if [ -f /swapfile ]; then
gum log -sl info 'Running sudo swapon /swapfile'
sudo swapon /swapfile
if cat /etc/fstab | grep "/swapfile"; then
sudo sed -i '/\/swapfile/\/swapfile none swap defaults 0 0/' /etc/fstab
else
echo "/swapfile none swap defaults 0 0" | sudo tee -a /etc/fstab > /dev/null
fi
fi
fi
fi
}
# @description
# This script imports your publicly hosted GPG key using `pgp.mit.edu` as the key host. It then assigns it
# the ultimate trust level. It also downloads and configures GPG to use the configuration defined in `.config.gpg`
# in the `home/.chezmoidata.yaml` file.
configureGPG() {
export KEYID="{{ .user.gpg.id }}"
if [ -n "$KEYID" ] && command -v gpg > /dev/null; then
if [ ! -d "$HOME/.gnupg" ]; then
mkdir "$HOME/.gnupg"
fi
chown "$(whoami)" "$HOME/.gnupg"
chmod 700 "$HOME/.gnupg"
chown -Rf "$(whoami)" "$HOME/.gnupg/"
find "$HOME/.gnupg" -type f -exec chmod 600 {} \;
find "$HOME/.gnupg" -type d -exec chmod 700 {} \;
if [ ! -f "$HOME/.gnupg/gpg.conf" ]; then
logg 'Downloading hardened gpg.conf file to ~/.gpnupg/gpg.conf'
curl -sSL --compressed "{{ .config.gpg }}" > "$HOME/.gnupg/gpg.conf"
chmod 600 "$HOME/.gnupg/gpg.conf"
fi
gum log -sl info 'Killing dirmngr instance and reloading daemon with standard-resolver' && sudo pkill dirmngr
dirmngr --daemon --standard-resolver
KEYID_TRIMMED="$(echo "$KEYID" | sed 's/^0x//')"
if ! gpg --list-secret-keys --keyid-format=long | grep "$KEYID_TRIMMED" > /dev/null; then
if [ -f "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/private_dot_gnupg/private_public/private_${KEYID}.asc" ]; then
gum log -sl info "Importing GPG key stored in ${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/private_dot_gnupg/private_public/private_${KEYID}.asc since its name matches the GPG key ID in .chezmoi.yaml.tmpl"
gpg --import "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/private_dot_gnupg/private_public/private_${KEYID}.asc" && gum log -sl info 'Successfully imported master GPG key'
else
gum log -sl info 'Attempting to download the specified public GPG key ({{ .user.gpg.id }}) from public keyservers'
gpg --keyserver https://pgp.mit.edu --recv "$KEYID" || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
gum log -sl info 'Non-zero exit code received when downloading public GPG key'
gpg --keyserver hkps://pgp.mit.edu --recv "$KEYID" || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
gum log -sl info 'Non-zero exit code received when trying to retrieve public user GPG key on hkps://pgp.mit.edu'
else
gum log -sl info 'Successfully imported configured public user GPG key'
fi
fi
fi
else
gum log -sl info 'Key is already in keyring'
fi
gum log -sl info 'Stopping dirmngr'
gpgconf --kill dirmngr && gum log -sl info 'Stopped dirmngr' || info warn 'Failed to stop dirmngr'
logg 'Ensuring the trust of the provided public GPG key is set to maximum'
echo -e "trust\n5\ny" | gpg --command-fd 0 --edit-key "$KEYID"
else
gum log -sl warn 'gpg appears to be unavailable. Is it installed and on the PATH?'
fi
}
# @description Disable the creation of `.DS_Store` files on macOS.
disableDStoreFileCreation() {
if command -v m > /dev/null; then
gum log -sl info 'Disabling creation of .DS_Store files'
echo y | m dir dsfiles off > /dev/null
fi
}
# @description Enables transparent dark-mode on macOS
enableDarkTransparentMode() {
if command -v m > /dev/null; then
gum log -sl info 'Enabling dark mode' && m appearance darkmode YES > /dev/null
gum log -sl info 'Enabling theme transparency' && m appearance transparency YES > /dev/null
fi
}
# @description Helper function for installing Homebrew packages that have a matching package name and binary executable name.
ensureBrewPackageInstalled() {
if ! command -v "$1" > /dev/null; then
if command -v brew; then
gum log -sl info "Installing $1 via Homebrew"
brew install --quiet "$1" || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
gum log -sl error "$1 was not successfully installed via Homebrew"
unset EXIT_CODE
fi
else
gum log -sl error "brew is unavailable. Cannot use it to perform installation of $1"
fi
else
gum log -sl info "$1 is already installed"
fi
}
# @description Ensure delta is installed via Homebrew
ensureDeltaInstalled() {
if ! command -v delta > /dev/null; then
if command -v brew; then
logg 'Installing delta via Homebrew'
brew install --quiet git-delta || DELTA_EXIT_CODE=$?
if [ -n "$DELTA_EXIT_CODE" ]; then
gum log -sl error 'git-delta was not successfully installed via Homebrew'
fi
else
logg 'brew is unavailable. Cannot use it to perform a system installation of node.'
fi
else
logg 'delta is available'
fi
}
# @description Ensure Node is installed via Homebrew
ensureNodeInstalled() {
### Ensure node is installed
if ! command -v node > /dev/null; then
if command -v brew; then
logg 'Installing node via Homebrew'
brew install --quiet node || NODE_EXIT_CODE=$?
if [ -n "$NODE_EXIT_CODE" ]; then
gum log -sl warn 'Calling brew link --overwrite node because the Node.js installation seems to be misconfigured'
brew link --overwrite node
fi
else
logg 'brew is unavailable. Cannot use it to perform a system installation of node.'
fi
else
logg 'node is available'
fi
}
# @description
# This script ensures that there is a group with the same name of the provisioning user available on the system.
ensureUserGroup() {
if [ "{{ .host.distro.family }}" = "darwin" ]; then
if [ -n "$USER" ]; then
gum log -sl info "Adding the $USER user to the $USER group"
### Ensure user has group of same name (required for Macports)
gum log -sl info "Ensuring user ($USER) has a group with the same name ($USER) and that it is a member. Sudo privileges may be required"
GROUP="$USER"
USERNAME="$USER"
### Add group
sudo dscl . create /Groups/$GROUP
### Add GroupID to group
if [[ "$(sudo dscl . read /Groups/$GROUP gid 2>&1)" == *'No such key'* ]]; then
MAX_ID_GROUP="$(dscl . -list /Groups gid | awk '{print $2}' | sort -ug | tail -1)"
GROUP_ID="$((MAX_ID_GROUP+1))"
sudo dscl . create /Groups/$GROUP gid "$GROUP_ID"
fi
### Add user
sudo dscl . create /Users/$USERNAME
### Add PrimaryGroupID to user
if [[ "$(sudo dscl . read /Users/$USERNAME PrimaryGroupID 2>&1)" == *'No such key'* ]]; then
sudo dscl . create /Users/$USERNAME PrimaryGroupID 20
fi
### Add UniqueID to user
if [[ "$(sudo dscl . read /Users/$USERNAME UniqueID 2>&1)" == *'No such key'* ]]; then
MAX_ID_USER="$(dscl . -list /Users UniqueID | awk '{print $2}' | sort -ug | tail -1)"
USER_ID="$((MAX_ID_USER+1))"
sudo dscl . create /Users/$USERNAME UniqueID "$USERID"
fi
### Add user to group
sudo dseditgroup -o edit -t user -a $USERNAME $GROUP
else
gum log -sl warn 'The USER environment variable is unavailable'
fi
fi
}
# @description Increases the amount of memory a process can consume on Linux. In the case of `netdata` and other programs, many systems will suggest
# increasing the `vm.max_map_count`. According to a [RedHat article](https://access.redhat.com/solutions/99913), the default value is `65530`.
# This function increases that value to `262144` if `sysctl` is available on the system.
increaseMapCount() {
if [ ! -d /Applications ] && [ ! -d /System ]; then
### Linux
if command -v sysctl > /dev/null; then
gum log -sl info 'Increasing vm.max_map_count size to 262144'
sudo sysctl -w vm.max_map_count=262144 > /dev/null
fi
fi
}
# @description Helper function for installDocker that installs pre-built gVisor using method recommended on official website
function gVisorPreBuilt() {
gum log -sl info 'Installing gVisor using method recommended on official website'
set -e
mkdir /tmp/gvisor && cd /tmp/gvisor
ARCH=$(uname -m)
URL="https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}"
gum log -sl info 'Downloading gVisor runsc and containerd-shim-runsc-v1 SHA signatures'
wget "${URL}/runsc" "${URL}/runsc.sha512" "${URL}/containerd-shim-runsc-v1" "${URL}/containerd-shim-runsc-v1.sha512"
sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
}
# @description Helper function for installDocker that installs gVisor using alternate Go method described on the GitHub page
function gVisorGo() {
# Official build timed out - use Go method
gum log -sl info 'Installing gVisor using the Go fallback method'
sudo chown -Rf "$(whoami)" /usr/local/src/gvisor
cd /usr/local/src/gvisor
echo "module runsc" > go.mod
GO111MODULE=on go get gvisor.dev/gvisor/runsc@go
CGO_ENABLED=0 GO111MODULE=on sudo -E go build -o /usr/local/bin/runsc gvisor.dev/gvisor/runsc
GO111MODULE=on sudo -E go build -o /usr/local/bin/containerd-shim-runsc-v1 gvisor.dev/gvisor/shim
}
# @description Helper function for installDocker that installs gVisor using the [GitHub developer page method](https://github.com/google/gvisor#installing-from-source). This method requires Docker to be installed
function gVisorSource() {
### Ensure sources are cloned / up-to-date
gum log -sl info 'Building gVisor from source'
if [ -d /usr/local/src/gvisor ]; then
cd /usr/local/src/gvisor
sudo git reset --hard HEAD
sudo git clean -fxd
sudo git pull origin master
else
sudo git clone https://github.com/google/gvisor.git /usr/local/src/gvisor
fi
### Build gVisor
cd /usr/local/src/gvisor
sudo mkdir -p bin
# Wait 5 minutes for build to finish, and if it does not use Go
# TODO - Generate container-shim-runsc-v1 as well (low priority since this method is not used and is only recommended for development)
sudo timeout 600 make copy TARGETS=runsc DESTINATION=bin/
if [ -f ./bin/runsc ]; then
sudo cp ./bin/runsc /usr/local/bin
else
gum log -sl error 'Timed out while building runsc from source (10 minutes)' && exit 6
fi
}
# @description Helper function for installDocker that Installs systemsecret credential helper for Linux
function installCredentialSecretService() {
curl -sSL https://github.com/docker/docker-credential-helpers/releases/download/v0.7.0/docker-credential-secretservice-v0.7.0.linux-amd64 > /tmp/docker-credential-secretservice
sudo mv /tmp/docker-credential-secretservice /usr/local/bin/docker-credential-secretservice
}
# @description
# This script ensures Docker is installed and then adds the provisioning user to the `docker` group so that they can
# access Docker without `sudo`. It also installs and configures gVisor for use with Docker.
#
# #### gVisor
#
# gVisor is included with our Docker setup because it improves the security of Docker. gVisor is an application kernel, written in Go,
# that implements a substantial portion of the Linux system call interface. It provides an additional layer of isolation between running
# applications and the host operating system. It has gained a lot of attention, perhaps partly, because it is maintained by Google.
installDocker() {
### Ensures `~/.config/docker` is symlinked to `~/.docker` which is required for Docker Desktop compatibility since it currently does not honor XDG spec. This will
### remove the current configuration at `~/.docker` if it is present and not symlinked to `~/.config/docker`.
if [ "$(readlink -f "$HOME/.docker")" != "${XDG_CONFIG_HOME:-$HOME/.config}/docker" ]; then
gum log -sl info 'Removing ~/.docker if present' && rm -rf "$HOME/.docker"
gum log -sl info 'Ensuring ~/.config/docker exists' && mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/docker"
gum log -sl info 'Symlinking ~/.config/docker to ~/.docker for Docker Desktop compatibility' && ln -s "${XDG_CONFIG_HOME:-$HOME/.config}/docker" "$HOME/.docker"
else
gum log -sl info 'Symlink from ~/.config/docker to ~/.docker is already present'
fi
### Install Docker
if [ -d /Applications ] && [ -d /System ]; then
### macOS
if [ ! -d /Applications/Docker.app ]; then
gum log -sl info 'Installing Docker on macOS via Homebrew cask'
brew install --cask --quiet --no-quarantine docker
else
gum log -sl info 'Docker appears to be installed already'
fi
gum log -sl info 'Opening the Docker for Desktop app so that the Docker engine starts running'
# TODO - --install-privileged-components may be necessary for `docker extension` command but it causes the command to no longer work
# open --background -a Docker --args --accept-license --unattended --install-privileged-components
open --background -a Docker --args --accept-license --unattended
elif command -v apt-get > /dev/null; then
. /etc/os-release
if [ "$ID" == 'ubuntu' ]; then
gum log -sl info 'Installing Docker on Ubuntu'
else
gum log -sl info 'Installing Docker on Debian'
fi
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL "https://download.docker.com/linux/$ID/gpg" | sudo gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$ID $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
elif command -v dnf > /dev/null; then
. /etc/os-release
if [ "$ID" == 'centos' ]; then
gum log -sl info 'Installing Docker on CentOS'
elif [ "$ID" == 'fedora' ]; then
gum log -sl info 'Installing Docker on Fedora'
else
gum log -sl error 'Unknown OS - cannot install Docker' && exit 1
fi
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo "https://download.docker.com/linux/$ID/docker-ce.repo"
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
elif command -v yum > /dev/null; then
# CentOS
gum log -sl info 'Installing Docker on CentOS'
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
elif command -v apk > /dev/null; then
# Alpine
gum log -sl info 'Installing Docker on Alpine'
sudo apk add --update docker
elif command -v pacman > /dev/null; then
# Archlinux
gum log -sl info 'Installing Docker on Archlinux'
sudo pacman -Syu
sudo pacman -S docker
elif command -v zypper > /dev/null; then
# OpenSUSE
gum log -sl info 'Installing Docker on OpenSUSE'
sudo zypper addrepo https://download.docker.com/linux/sles/docker-ce.repo
sudo zypper install docker-ce docker-ce-cli containerd.io docker-compose-plugin
fi
### Add Docker group on Linux
if command -v groupadd > /dev/null; then
# Linux
if ! cat /etc/group | grep docker > /dev/null; then
gum log -sl info 'Creating Docker group'
sudo groupadd docker
fi
gum log -sl info 'Adding user to Docker group'
sudo usermod -aG docker "$USER"
fi
### Boot Docker on start with systemd on Linux machines
if command -v systemctl > /dev/null; then
# Systemd Linux
sudo systemctl start docker.service
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
fi
### Add gVisor
if [ ! -d /Applications ] || [ ! -d /System ]; then
### Linux
if ! command -v docker-credential-secretservice > /dev/null; then
installCredentialSecretService
fi
if ! command -v runsc > /dev/null; then
### Install gVisor
gVisorPreBuilt || PRE_BUILT_EXIT_CODE=$?
if [ -n "$PRE_BUILT_EXIT_CODE" ]; then
gum log -sl warn 'gVisor failed to install using the pre-built method'
gVisorGo || GO_METHOD_EXIT_CODE=$?
if [ -n "$GO_METHOD_EXIT_CODE" ]; then
gum log -sl warn 'gVisor failed to install using the Go fallback method'
gVisorSource || SOURCE_EXIT_CODE=$?
if [ -n "$SOURCE_EXIT_CODE" ]; then
gum log -sl error 'All gVisor installation methods failed' && exit 1
else
gum log -sl info 'gVisor installed via source'
fi
else
gum log -sl info 'gVisor installed via Go fallback method'
fi
else
gum log -sl info 'gVisor installed from pre-built Google-provided binaries'
fi
else
gum log -sl info 'runsc is installed'
fi
### Ensure Docker is configured to use runsc
if [ ! -f /etc/docker/daemon.json ]; then
### Configure Docker to use gVisor
### Create /etc/docker/daemon.json
gum log -sl info 'Creating /etc/docker'
sudo mkdir -p /etc/docker
if [ -f "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/dot_config/docker/daemon.json.tmpl" ]; then
gum log -sl info 'Creating /etc/docker/daemon.json'
chezmoi cat "${XDG_CONFIG_HOME:-$HOME/.config}/docker/config.json" | sudo tee /etc/docker/daemon.json
else
gum log -sl warn "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/dot_config/docker/daemon.json.tmpl is not available so the /etc/docker/daemon.json file cannot be populated"
fi
### Restart / enable Docker
if [[ ! "$(test -d /proc && grep Microsoft /proc/version > /dev/null)" ]] && command -v systemctl > /dev/null; then
gum log -sl info 'Restarting Docker service'
sudo systemctl restart docker.service
sudo systemctl restart containerd.service
fi
### Test Docker /w runsc
gum log -sl info 'Testing that Docker can load application with runsc'
docker run --rm --runtime=runsc hello-world || RUNSC_EXIT_CODE=$?
if [ -n "$RUNSC_EXIT_CODE" ]; then
gum log -sl error 'Failed to run the Docker hello-world container with runsc' && exit 5
else
gum log -sl info 'Docker successfully ran the hello-world container with runsc'
fi
fi
fi
}
# @description
# This script enrolls the device as a JumpCloud managed asset. The `JUMPCLOUD_CONNECT_KEY` secret should
# be populated using one of the methods described in the [Secrets documentation](https://install.doctor/docs/customization/secrets).
#
# *Note: You should check out the supported systems before trying to enroll devices.*
#
# #### JumpCloud on macOS
#
# macOS offers a native device management feature offered through Apple Business. It is the preferred
# method since it offers most of the desirable features (like remote wipe). The [JumpCloud MDM documentation](https://support.jumpcloud.com/support/s/article/Getting-Started-MDM)
# details the steps required to register macOS MDM profiles with JumpCloud.
#
# ## Links
#
# * [JumpCloud device management requirements](https://support.jumpcloud.com/support/s/article/jumpcloud-agent-compatibility-system-requirements-and-impacts1)
installJumpCloud() {
if [ "{{ .host.distro.family }}" = "linux" ]; then
if [ "{{ if (stat (joinPath .chezmoi.sourceDir ".chezmoitemplates" "secrets" "JUMPCLOUD_CONNECT_KEY")) }}{{- includeTemplate "secrets/JUMPCLOUD_CONNECT_KEY" | decrypt | trim -}}{{ else }}{{- env "JUMPCLOUD_CONNECT_KEY" -}}{{ end }}" != "" ]; then
gum log -sl info 'Enrolling device with JumpCloud by running the kickstart script'
curl --tlsv1.2 --silent --show-error --header 'x-connect-key: {{ if (stat (joinPath .chezmoi.sourceDir ".chezmoitemplates" "secrets" "JUMPCLOUD_CONNECT_KEY")) }}{{- includeTemplate "secrets/JUMPCLOUD_CONNECT_KEY" | decrypt | trim -}}{{ else }}{{- env "JUMPCLOUD_CONNECT_KEY" -}}{{ end }}' https://kickstart.jumpcloud.com/Kickstart | sudo bash
fi
fi
}
# @description Installs commonly depended on Python packages
installSystemPips() {
### Upgrade on macOS
if [ -f /Library/Developer/CommandLineTools/usr/bin/python3 ]; then
gum log -sl info 'Ensuring macOS system python3 has latest version of pip'
/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip
fi
### Python 3
if command -v pip3 > /dev/null; then
if command -v python3 > /dev/null; then
if ! python3 -m certifi > /dev/null; then
pip3 install --break-system-packages certifi
else
gum log -sl info 'certifi is available to python3'
fi
else
gum log -sl warn 'python3 is not available on the system'
fi
else
gum log -sl warn 'pip3 is not available on the system'
fi
}
# @description
# This script removes some of the software deemed to be "bloatware" by cycling through the values defined in
# `.removeLinuxPackages` of the `home/.chezmoidata.yaml` file.
removeLinuxBloatware() {
if [ "{{ .host.distro.family }}" = "linux" ]; then
{{- $removePackages := join " " .removeLinuxPackages }}
### Remove bloatware packages defined in .chezmoidata.yaml
for PKG in {{ $removePackages }}; do
if command -v apk > /dev/null; then
if apk list "$PKG" | grep "$PKG" > /dev/null; then
sudo apk delete "$PKG"
fi
elif command -v apt-get > /dev/null; then
if dpkg -l "$PKG" | grep -E '^ii' > /dev/null; then
sudo apt-get remove -y "$PKG"
gum log -sl info 'Removed '"$PKG"' via apt-get'
fi
elif command -v dnf > /dev/null; then
if rpm -qa | grep "$PKG" > /dev/null; then
sudo dnf remove -y "$PKG"
gum log -sl info 'Removed '"$PKG"' via dnf'
fi
elif command -v yum > /dev/null; then
if rpm -qa | grep "$PKG" > /dev/null; then
sudo yum remove -y "$PKG"
gum log -sl info 'Removed '"$PKG"' via yum'
fi
elif command -v pacman > /dev/null; then
if pacman -Qs "$PKG" > /dev/null; then
sudo pacman -R "$PKG"
gum log -sl info 'Removed '"$PKG"' via pacman'
fi
elif command -v zypper > /dev/null; then
if rpm -qa | grep "$PKG" > /dev/null; then
sudo zypper remove -y "$PKG"
gum log -sl info 'Removed '"$PKG"' via zypper'
fi
fi
done
fi
}
# @description Sets the hostname using `scutil` on macOS and using `hostname` and `hostnamectl` on Linux. On macOS, the HostName, LocalHostName, and ComputerName
# are set equal to the value stored in `.host.hostname` (in `.chezmoi.yaml.tmpl`) but with the `.host.domain` stripped off. On Linux, the same is done
# but only the hostname is set. On Linux, the hostname is set with the `hostname` command and then also with the `hostnamectl` command if it is available.
#
# #### Sources
#
# * [Changing Linux hostname permanently](https://www.tecmint.com/set-hostname-permanently-in-linux/)
setHostname() {
HOSTNAME="{{ (.host.hostname | replace .host.domain "" | replace "." "" | replace " " "") | lower }}.{{ .host.domain | lower }}"
LOCAL_HOSTNAME="{{ (.host.hostname | replace .host.domain "" | replace "." "" | replace " " "") | lower }}"
COMPUTER_NAME="{{ .host.hostname }}"
if [ -d /Applications ] && [ -d /System ]; then
# Source: https://apple.stackexchange.com/questions/287760/set-the-hostname-computer-name-for-macos
gum log -sl info 'Setting macOS hostname / local hostname / computer name'
gum log -sl info "Changing HostName to $HOSTNAME" && sudo scutil --set HostName "$HOSTNAME" && gum log -sl info "Changed HostName to $HOSTNAME"
gum log -sl info "Changing LocalHostName to $LOCAL_HOSTNAME" && sudo scutil --set LocalHostName "$LOCAL_HOSTNAME" && gum log -sl info "Changed LocalHostName to $LOCAL_HOSTNAME"
gum log -sl info "Changing ComputerName to $COMPUTER_NAME" && sudo scutil --set ComputerName "$COMPUTER_NAME" && gum log -sl info "Changed ComputerName to $COMPUTER_NAME"
gum log -sl info 'Flushing DNS cache'
sudo dscacheutil -flushcache
elif [ -f /etc/passwd ]; then
gum log -sl info 'Setting Linux hostname'
sudo hostname "$HOSTNAME" && gum log -sl info "Changed hostname to $HOSTNAME"
if command -v hostnamectl > /dev/null; then
gum log -sl info 'Ensuring hostname persists after reboot'
sudo hostnamectl set-hostname "$HOSTNAME" && gum log -sl info "Permanently changed hostname to $HOSTNAME"
else
gum log -sl warn 'hostnamectl was not available in the PATH - this operating system type might be unsupported'
fi
else
gum log -sl warn 'Could not configure hostname because system type was not detectable'
fi
}
# @description Sets the NTP server using `m` on macOS
#
# `2>/dev/null 1>&2` was added after `sudo systemsetup -setusingnetworktime` calls because the command was outputting the following
# useless error:
#
# ```shell
# ### Error:-99 File:/AppleInternal/Library/BuildRoots/0032d1ee-80fd-11ee-8227-6aecfccc70fe/Library/Caches/com.apple.xbs/Sources/Admin/InternetServices.m Line:379
# ```
setNtpServer() {
if command -v m > /dev/null; then
### macOS
gum log -sl info 'Copying ~/.local/etc/ntp.conf to /etc/ntp.conf'
sudo cp -f "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/dot_local/etc/ntp.conf" /etc/ntp.conf
gum log -sl info 'Copying ~/.local/etc/ntp.conf to /private/etc/ntp.conf'
sudo cp -f "${XDG_DATA_HOME:-$HOME/.local/share}/chezmoi/home/dot_local/etc/ntp.conf" /private/etc/ntp.conf
gum log -sl info 'Turning off setusingnetworktime for 2 seconds' && sudo systemsetup -setusingnetworktime off 2>/dev/null 1>&2
sleep 2
gum log -sl info 'Re-enabling setusingnetworktime' && sudo systemsetup -setusingnetworktime on 2>/dev/null 1>&2
else
gum log -sl warn 'Skipped setting the NTP server'
fi
}
# @description Sets the system timezone using `timedatectl` on Linux and `m` on macOS. If neither commands are available
# then a warning message is printed.
setTimezone() {
if command -v timedatectl > /dev/null; then
### Linux
gum log -sl info 'Setting timezone to {{ .user.timezone }}'
sudo timedatectl set-timezone {{ .user.timezone }}
elif command -v systemsetup > /dev/null && [ -d /Applications ] && [ -d /System ]; then
### macOS
gum log -sl info 'Setting timezone to {{ .user.timezone }}' && sudo systemsetup -settimezone "{{ .user.timezone }}" 2>/dev/null 1>&2
else
gum log -sl warn 'Neither timedatectl (Linux) or systemsetup (macOS) were found on the system'
fi
}
# @description Configures macOS to enable the notification center. `&> /dev/null` was added to the command
# because the following useless error was being reported:
#
# ```shell
# Load failed: 5: Input/output error
# Try running `launchctl bootstrap` as root for richer errors.
# Restart your computer for this to take effect
# ```
showNotificationCenter() {
if command -v m > /dev/null; then
gum log -sl info 'Configuring macOS to show notification center' && m notification showcenter YES &> /dev/null
fi
}
installAnsible() {
if command -v pipx > /dev/null; then
if [ ! -f "${XDG_CACHE_HOME:-$HOME/.cache}/install.doctor/ansible-installed" ]; then
gum log -sl info 'Running pipx install ansible' && pipx install ansible
if [ -d /Applications ] && [ -d /System ]; then
gum log -sl info 'Injecting ansible pipx with ansible PyObjC PyObjC-core because system is macOS' && pipx inject ansible PyObjC PyObjC-core
fi
gum log -sl info 'Running pipx inject ansible docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog' && pipx inject ansible docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog
mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/install.doctor"
touch "${XDG_CACHE_HOME:-$HOME/.cache}/install.doctor/ansible-installed"
else
gum log -sl info 'Ansible installation routine appears to have already been run'
fi
else
gum log -sl warn 'pipx is unavailable to use for installing Ansible'
fi
}
installBrewPackages() {
ensureNodeInstalled
ensureDeltaInstalled
ensureBrewPackageInstalled "volta"
volta install node@latest &
volta install yarn@latest &
npm install --no-progress -g npm@latest &
ensureBrewPackageInstalled "pipx"
pipx ensurepath &
ensureBrewPackageInstalled "gh"
ensureBrewPackageInstalled "go"
ensureBrewPackageInstalled "ruby"
ensureBrewPackageInstalled "rustup"
ensureBrewPackageInstalled "zx"
ensureBrewPackageInstalled "whalebrew"
wait
gum log -sl info 'Finished installing auxilary Homebrew packages'
gum log -sl info 'Ensuring Ansible is installed (with plugins)' && installAnsible
}
ensureMacportsInstalled() {
if [ -d /Applications ] && [ -d /System ]; then
if ! command -v port > /dev/null; then
gum log -sl info 'Ensuring /opt/mports/macports-base is removed' && sudo rm -rf /opt/mports/macports-base
gum log -sl info 'Cloning source for macports to /opt/mports/macports-base' && sudo git clone --branch v2.8.0 --depth 1 https://github.com/macports/macports-base.git /opt/mports/macports-base
cd /opt/mports/macports-base
gum log -sl info 'Building macports' && sudo bash --noprofile --norc -c './configure --enable-readline && make && make install && make distclean'
gum log -sl info 'Adding /opt/local/bin to PATH because port is installed there'
export PATH="/opt/local/bin:$PATH"
gum log -sl info 'Running sudo port selfupdate' && sudo port selfupdate
fi
fi
}
setupSnap() {
if [ ! -d /Applications ] && [ ! -d /System ] && command -v snap > /dev/null; then
gum log -sl info 'Enabling snapd' && sudo systemctl enable snapd
gum log -sl info 'Starting snapd' && sudo systemctl start snapd
if [ -d /snap ]; then
gum log -sl info 'Linking /var/lib/snapd/snap to /snap' && sudo ln -s /var/lib/snapd/snap /snap
fi
gum log -sl info 'Running sudo snap info core' && sudo snap info core
gum log -sl info 'Running sudo snap wait system seed.loaded' && sudo snap wait system seed.loaded
gum log -sl info 'Running sudo snap install core' && sudo snap install core
fi
}
installYay() {
if [ -f /etc/arch-release ] && ! command -v yay > /dev/null; then
sudo rm -rf /usr/local/src/yay
sudo git clone https://aur.archlinux.org/yay.git /usr/local/src/yay
cd /usr/local/src/yay
sudo makepkg -si
fi
}
installNix() {
if ! command -v nix-shell > /dev/null; then
if [ -d /Applications ] && [ -d /System ]; then
### macOS
gum log -sl info 'Installing nix for macOS' && sh <(curl -L https://nixos.org/nix/install) --yes
else
### Linux
gum log -sl info 'Installing nix' && sh <(curl -L https://nixos.org/nix/install) --daemon --yes
fi
fi
}
rustUpInit() {
if command -v rustup-init > /dev/null && ! command -v rustc > /dev/null; then
gum log -sl info 'Running rustup-init -y' && rustup-init -y
fi
}
zapInstall() {
if [ ! -d /Applications ] && [ ! -d /System ]; then
if ! command -v zap > /dev/null; then
### Architecture
if [ -z ${ARCH+x} ]; then
MACHINE_ARCH="$(uname -m)"
if [ "$MACHINE_ARCH" = "amd64" ]; then
ARCH="amd64"
elif [ "$MACHINE_ARCH" = "x86_64" ]; then
ARCH="amd64"
elif [ "$MACHINE_ARCH" = "i386" ]; then
ARCH="386"
elif [ "$MACHINE_ARCH" = "i686" ]; then
ARCH="386" # both are 32bit, should be compatible
elif [ "$MACHINE_ARCH" = "aarch64" ]; then
ARCH="arm64"
elif [ "$MACHINE_ARCH" = "arm64" ]; then
ARCH="arm64"
elif [ "$MACHINE_ARCH" = "arm" ]; then
ARCH="arm"
fi
export ARCH
fi
gum log -sl info 'Downloading zap to /usr/local/bin/zap' && sudo curl -sSL --output /usr/local/bin/zap "https://github.com/srevinsaju/zap/releases/download/continuous/zap-${ARCH}"
gum log -sl info 'Making /usr/local/bin/zap executable' && sudo chmod +x /usr/local/bin/zap
fi
fi
}
addFlathub() {
if command -v flatpak > /dev/null; then
gum log -sl info 'Adding flatpak flathub repository' && sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
fi
}
installXcode() {
if [ -d /Applications ] && [ -d /System ]; then
if [ ! -d /Applications/Xcode.app ]; then
gum log -sl info 'Installing Xcode via mas - the installation will timeout after 40 minutes if Apple account is not signed into'
timeout 2400 mas install 497799835 || MAS_EXIT_CODE=$?
if [ -n "$MAS_EXIT_CODE" ]; then
gum log -sl error 'Failed to install Xcode'
fi
else
gum log -sl info 'Xcode is already installed'
fi
fi
}
setupLinuxHomebrewFonts() {
if [ ! -d /Applications ] && [ ! -d /System ]; then
gum log -sl info 'Tapping homebrew/linux-fonts' && brew tap homebrew/linux-fonts
gum log -sl info 'Symlinking linuxbrew/share/fonts to /usr/local/share' && sudo ln -s /home/linuxbrew/.linuxbrew/share/fonts -t /usr/local/share
if command -v fc-cache > /dev/null; then
gum log -sl info 'Running sudo fc-cache -fv' && sudo fc-cache -fv
else
gum log -sl warn 'fc-cache binary not available for setting up Linux Homebrew fonts'
fi
fi
}
miscMacOs() {
if [ ! -d "$HOME/Library/PreferencePanes" ]; then
gum log -sl info 'Ensuring $HOME/Library/PreferencePanes exists as a folder' && mkdir -p "$HOME/Library/PreferencePanes"
fi
}
# TODO - Add install on macOS for macports
if [ -n "$DEBUG" ] || [ -n "$DEBUG_MODE" ]; then
gum log -sl info 'The DEBUG or DEBUG_MODE environment variable is set so preliminary system tweaks will be run synchronously'
installXcode
addFlathub
allocateSwap
configureGPG
disableDStoreFileCreation
enableDarkTransparentMode
ensureMacportsInstalled
ensureUserGroup
increaseMapCount
installBrewPackages
installDocker
installJumpCloud
installSystemPips
installYay
removeLinuxBloatware
rustUpInit
setHostname
setNtpServer
setTimezone
setupSnap
showNotificationCenter
zapInstall
setupLinuxHomebrewFonts
miscMacOs
else
installXcode &
addFlathub &
allocateSwap &
configureGPG &
disableDStoreFileCreation &
enableDarkTransparentMode &
ensureMacportsInstalled &
ensureUserGroup &
increaseMapCount &
installBrewPackages &
installDocker &
installJumpCloud &
installSystemPips &
installYay &
removeLinuxBloatware &
rustUpInit &
setHostname &
setNtpServer &
setTimezone &
setupSnap &
showNotificationCenter &
zapInstall &
setupLinuxHomebrewFonts &
miscMacOs &
wait
fi
gum log -sl info 'Successfully applied preliminary system tweaks'