install.fairie/home/.chezmoiscripts/universal/run_after_01-pre-install.sh.tmpl
2023-12-24 22:36:54 +00:00

1008 lines
50 KiB
Bash

#!/usr/bin/env bash
# @description Copies the `~/.config/shell/exports.sh` file to `/etc/zshenv` so that non-interactive ZSH sessions have all of the same
# environment PATH variables as interactive sessions. This was initially required to make Cakebrew work on macOS.
addZshEnv() {
### Ensure /etc/zshenv is populated
# No equivalent type of file for Bash
logg info "Copying ${XDG_CONFIG_HOME:-$HOME/.config}/shell/exports.sh to /etc/zshenv" && sudo cp -f "${XDG_CONFIG_HOME:-$HOME/.config}/shell/exports.sh" /etc/zshenv
}
# @description Ensures fonts are available at the system level and, on Linux, it configures the system font settings.
applyFontsToSystem() {
### Sync user fonts with system fonts
if [ -d /Applications ] && [ -d /System ]; then
### macOS
logg info 'Copying fonts from ~/Library/Fonts and ~/.local/share/fonts to /Library/Fonts to make them available globally'
FONT_DIR='/Library/Fonts'
sudo rsync -av "$HOME/Library/Fonts" "$FONT_DIR"
sudo rsync -av "${XDG_DATA_HOME:-$HOME/.local/share}/fonts" "$FONT_DIR"
else
### Linux
logg info 'Copying fonts from ~/.local/share/fonts to /usr/local/share/fonts to make them available globally'
FONT_DIR='/usr/local/share/fonts'
sudo rsync -av "${XDG_DATA_HOME:-$HOME/.local/share}/fonts" "$FONT_DIR"
fi
### Configure system font properties
if [ -d /etc/fonts ]; then
logg info 'Copying ~/.config/fontconfig/fonts.conf to /etc/fonts/local.conf'
sudo cp -f "${XDG_CONFIG_HOME:-$HOME/.config}/fontconfig/fonts.conf" /etc/fonts/local.conf
else
logg warn 'The /etc/fonts directory is missing'
fi
}
# @description Applies various `dconf`, `xconf`, etc. settings to Linux systems
applyLinuxConfSettings() {
if [ "{{ .host.distro.family }}" = "linux" ] && command -v apply-linux-conf-settings > /dev/null; then
apply-linux-conf-settings
fi
}
# @description
# This script applies various themes to Linux systems that use GNOME or KDE.
applyLinuxThemeFiles() {
if [ "{{ .host.distro.family }}" = "linux" ]; then
### Ensure /usr/local/bin/squash-symlink is present
if [ ! -f /usr/local/bin/squash-symlink ] && [ -f "$HOME/.local/bin/squash-symlink" ]; then
logg info 'Copying ~/.local/bin/squash-symlink to /usr/local/bin/squash-symlink'
sudo cp -f "$HOME/.local/bin/squash-symlink" /usr/local/bin/squash-symlink
sudo chmod +x /usr/local/bin/squash-symlink
fi
### Clean up system theme settings
for ITEM_TO_BE_REMOVED in "/usr/share/backgrounds/images" "/usr/share/backgrounds/f32" "/usr/share/backgrounds/qubes" "/usr/share/wallpapers"; do
if [ -d "$ITEM_TO_BE_REMOVED" ] || [ -f "$ITEM_TO_BE_REMOVED" ]; then
sudo rm -rf "$ITEM_TO_BE_REMOVED"
logg success "Removed $ITEM_TO_BE_REMOVED"
fi
done
### Ensure /usr/local/share exists
if [ ! -d /usr/local/share ]; then
sudo mkdir -p /usr/local/share
logg success 'Created /usr/local/share'
fi
### Copy theme files over to /usr/local/share
if [ -d "$HOME/.local/src/{{ .theme | lower }}/share" ]; then
logg info 'Copying ~/.local/src/{{ .theme | lower }}/share to /usr/local/share'
sudo rsync --chown=root:root --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r -artvu --inplace "${XDG_DATA_HOME:-$HOME/.local/share}/betelgeuse/share/" "/usr/local/share/" > /dev/null
else
logg warn '~/.local/share/betelgeuse/share is missing'
fi
### Flatten GRUB theme files (i.e. convert symlinks to regular files)
if command -v squash-symlink > /dev/null; then
logg info 'Converting /usr/local/share/grub symlinks to equivalent regular files'
sudo find /usr/local/share/grub -type l -exec squash-symlink {} +
else
logg warn 'squash-symlink is not a script in the PATH'
fi
### Ensure /usr/share/backgrounds/default.png is deleted
if [ -f /usr/share/backgrounds/default.png ]; then
sudo rm -f /usr/share/backgrounds/default.png
fi
### Add the default image symlink based on the OS
if [ '{{ .host.distro.id }}' == 'archlinux' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-Archlinux/contents/source.png /usr/share/backgrounds/default.png
elif [ '{{ .host.distro.id }}' == 'centos' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-CentOS/contents/source.png /usr/share/backgrounds/default.png
elif [ '{{ .host.distro.id }}' == 'darwin' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-macOS/contents/source.png /usr/share/backgrounds/default.png
elif [ '{{ .host.distro.id }}' == 'debian' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-Debian/contents/source.png /usr/share/backgrounds/default.png
elif [ '{{ .host.distro.id }}' == 'fedora' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-Fedora/contents/source.png /usr/share/backgrounds/default.png
elif [ '{{ .host.distro.id }}' == 'ubuntu' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-Ubuntu/contents/source.png /usr/share/backgrounds/default.png
elif [ '{{ .host.distro.id }}' == 'windows' ]; then
sudo ln -s /usr/local/share/wallpapers/Betelgeuse-Windows/contents/source.png /usr/share/backgrounds/default.png
else
sudo ln -s /usr/local/share/wallpapers/Betelgeuse/contents/source.png /usr/share/backgrounds/default.png
fi
### Note: ubuntu-default-greyscale-wallpaper.png symlink to whiteish gray background
### Set appropriate platform-specific icon in plymouth theme
if [ -f '/usr/local/share/plymouth/themes/{{ .theme }}/icons/{{ .host.distro.id }}.png' ]; then
sudo cp -f '/usr/local/share/plymouth/themes/{{ .theme }}/icons/{{ .host.distro.id }}.png' '/usr/local/share/plymouth/themes/{{ .theme }}/icon.png'
logg success 'Added platform-specific icon to {{ .theme }} Plymouth theme'
else
logg warn 'The {{ .host.distro.id }}.png icon is not available in the icons folder insider the {{ .theme }} Plymouth theme'
fi
fi
}
# @description
# This script configures the root user's folder so that scripts running as the root user can:
#
# 1. Access binaries installed by the provisioning user (by setting the appropriate `~/.bashrc` and `~/.zshrc` symlinks)
# 2. Use the same shell profile rules that the provisioning user uses (by symlinking the `~/.config/shell`, `~/.bashrc`, and `~/.zshrc` locations)
applyRootConfig() {
### Detect root folder
if [ -d /var/root ]; then
ROOT_FOLDER="/var/root"
elif [ -d /root ]; then
ROOT_FOLDER="/root"
else
logg warn 'Unable to find root user folder location'
fi
if [ -n "$ROOT_FOLDER" ]; then
### Copy minimal set of profile configuration files
logg info "Copying ~/.bashrc to $ROOT_FOLDER/.bashrc" && sudo cp -f "$HOME/.bashrc" "$ROOT_FOLDER/.bashrc"
logg info "Copying ~/.zshrc to $ROOT_FOLDER/.zshrc" && sudo cp -f "$HOME/.zshrc" "$ROOT_FOLDER/.zshrc"
logg info "Ensuring ~/.config folder exists" && sudo mkdir -p "$ROOT_FOLDER/.config"
logg info "Copying ~/.config/shell to $ROOT_FOLDER/.config/shell" && sudo mkdir -p "$ROOT_FOLDER/.config" && sudo rm -rf "$ROOT_FOLDER/.config/shell" && sudo cp -rf "$HOME/.config/shell" "$ROOT_FOLDER/.config/shell"
### Copy Autorestic configurations
logg info "Copying ${XDG_CONFIG_HOME:-$HOME/.config}/autorestic/autorestic-system.yml file to $ROOT_FOLDER/.autorestic.yml" && sudo cp -f "${XDG_CONFIG_HOME:-$HOME/.config}/autorestic/autorestic-system.yml" "$ROOT_FOLDER/.autorestic.yml"
logg info "Removing ${XDG_CONFIG_HOME:-$HOME/.config}/autorestic/autorestic-system.yml" && sudo rm -f "${XDG_CONFIG_HOME:-$HOME/.config}/autorestic/autorestic-system.yml"
logg info "Applying proper permissions to $ROOT_FOLDER/.autorestic.yml" && sudo chmod 600 "$ROOT_FOLDER/.autorestic.yml"
fi
}
# @description
# This function ensures the macOS desktop wallpaper is set to the macOS Betelgeuse wallpaper. It uses the
# `m` CLI to apply the change.
#
# This function ensures the Qubes desktop wallpaper is set to the Qubes Betelgeuse wallpaper on KDE by
# using the `ksetwallpaper` script found in `~/.local/bin/ksetwallpaper`.
applyWallpaper() {
{{- if (eq .host.distro.id "qubes") -}}
logg info 'Setting wallpaper to /usr/local/share/wallpapers/Betelgeuse/contents/images/3440x1440.jpg'
ksetwallpaper --file /usr/local/share/wallpapers/Betelgeuse/contents/images/3440x1440.jpg
{{ else -}}
### Set macOS wallpaper
if command -v m > /dev/null && [ -f "${XDG_DATA_HOME:-$HOME/.local/share}/betelgeuse/share/wallpapers/Betelgeuse-macOS/contents/source.png" ]; then
logg info 'Setting macOS wallpaper with m'
m wallpaper "${XDG_DATA_HOME:-$HOME/.local/share}/betelgeuse/share/wallpapers/Betelgeuse-macOS/contents/source.png"
else
logg warn 'Either m or the macOS default wallpaper is missing.'
fi
{{ end -}}
}
# @description
# This script ensures ASDF is setup and then adds the plugins specified in the `~/.tool-versions` file. After that,
# it ensures the ASDF plugins are pre-installed.
asdfPluginInstall() {
### Re-source ~/.bashrc if necessary
if [ -z "$ASDF_DIR" ]; then
logg info 'ASDF_DIR is not defined so ~/.bashrc will be sourced' && source ~/.bashrc
fi
if [ -f "$ASDF_DIR/asdf.sh" ] && [ -f ~/.tool-versions ]; then
logg info 'Sourcing asdf.sh'
. ${ASDF_DIR}/asdf.sh
cat .tool-versions | while read TOOL; do
logg info 'Installing ASDF plugin '"$(echo "$TOOL" | sed 's/ .*//')"'' && asdf plugin add "$(echo "$TOOL" | sed 's/ .*//')" > /dev/null && logg info "Successfully added $(echo "$TOOL" | sed 's/ .*//') via ASDF"
done
# Only proceed with installation if either DEBUG_MODE is enabled or ~/.cache/megabyte-labs/asdf-install is missing
# Added to save time between tests because PHP takes awhile to install
if [ "$DEBUG_MODE" == 'true' ] || [ ! -f "${XDG_CACHE_HOME:-$HOME/.cache}/megabyte-labs/asdf-install" ]; then
logg info 'Installing ASDF dependencies derived from ~/.tool-versions via asdf install'
asdf install || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
logg error 'Error installing the ASDF plugins specified in ~/.tool-versions'
fi
mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/megabyte-labs"
touch "${XDG_CACHE_HOME:-$HOME/.cache}/megabyte-labs/asdf-install"
fi
else
logg warn 'The $ASDF_DIR/asdf.sh or ~/.tool-versions file is not present'
fi
}
### Helper function for configureNetworkManager
ensureNetworkConfigs() {
if [ ! -d /etc/network/if-up.d ]; then
logg info 'Creating /etc/network/if-up.d folder'
sudo mkdir -p /etc/network/if-up.d
fi
if [ ! -d /etc/network/if-post-down.d ]; then
logg info 'Creating /etc/network/if-post.d folder'
sudo mkdir -p /etc/network/if-post.d
fi
}
# @description
# This script installs OpenVPN and WireGuard VPN profiles. It does a few things to install the profiles and make sure
# they are usable by desktop users:
#
# 1. It ensures OpenVPN and `NetworkManager-*` plugins are installed (this allows you to see all the different VPN profile types available when you try to import a VPN profile on Linux devices)
# 2. Imports the OpenVPN profiles stored in `${XDG_CONFIG_HOME:-$HOME/.config}/vpn`
# 3. Applies the OpenVPN username and password to all the OpenVPN profiles (which can be passed in as `OVPN_USERNAME` and `OVPN_PASSWORD` if you use the environment variable method)
# 4. Bypasses the OpenVPN connection for all the networks defined in `.host.vpn.excludedSubnets` (in the `home/.chezmoi.yaml.tmpl` file)
# 5. Repeats the process for WireGuard by looping through all the `*.nmconnection` files stored in `${XDG_CONFIG_HOME:-$HOME/.config}/vpn` (username and password should already be stored in the encrypted files)
#
# ## Creating VPN Profiles
#
# More details on embedding your VPN profiles into your Install Doctor fork can be found by reading the [Secrets documentation](https://install.doctor/docs/customization/secrets#vpn-profiles).
#
# ## Links
#
# * [VPN profile folder](https://github.com/megabyte-labs/install.doctor/blob/master/home/dot_config/vpn)
# * [VPN profile documentation](https://install.doctor/docs/customization/secrets#vpn-profiles)
configureNetworkManagerVPNProfiles() {
if [ "{{ .host.distro.family }}" = "linux" ]; then
{{ $ovpnUsername := (env "OVPN_USERNAME") }}
{{ if (stat (joinPath .chezmoi.sourceDir ".chezmoitemplates" "secrets" "OVPN_USERNAME")) }}
{{ $ovpnUsername := (includeTemplate "secrets/OVPN_USERNAME" | decrypt | trim) }}
{{ end }}
{{ $ovpnPassword := (env "OVPN_PASSWORD") }}
{{ if (stat (joinPath .chezmoi.sourceDir ".chezmoitemplates" "secrets" "OVPN_PASSWORD")) }}
{{ $ovpnPassword := (includeTemplate "secrets/OVPN_PASSWORD" | decrypt | trim) }}
{{ end }}
RESTART_NM=false
### Ensure `NetworkManager` plugins are
# NOTE: By default, all the NetworkManager plugins are installed.
if command -v apt-get > /dev/null; then
sudo apt-get install -y network-manager*
elif command -v dnf > /dev/null; then
sudo dnf install -y openvpn NetworkManager*
elif command -v pacman > /dev/null; then
sudo pacman -Syu openvpn networkmanager*
else
logg warn 'Unknown package manager - install OpenVPN / WireGuard / NetworkManager plugins individually'
fi
### Ensures `nmcli` (the CLI for NetworkManager) is available in the `PATH`
if command -v nmcli > /dev/null; then
### Sets up OpenVPN profiles
if [ '{{ $ovpnUsername }}' != '' ] && [ '{{ $ovpnPassword }}' != '' ]; then
find "${XDG_CONFIG_HOME:-$HOME/.config}/vpn" -type f -name "*.ovpn" | while read OVPN_FILE; do
### Adds the OpenVPN profiles by importing the `*.ovpn` files in `${XDG_CONFIG_HOME:-$HOME/.config}/vpn` and then applying the OpenVPN username and password
logg info "Adding $OVPN_FILE to NetworkManager OpenVPN profiles"
OVPN_NAME="$(basename "$OVPN_FILE" | sed 's/.ovpn$//')"
nmcli connection import type openvpn file "$OVPN_FILE"
nmcli connection modify "$OVPN_NAME" +vpn.data 'username={{- $ovpnUsername }}'
nmcli connection modify "$OVPN_NAME" vpn.secrets 'password={{- $ovpnPassword }}'
nmcli connection modify "$OVPN_NAME" +vpn.data password-flags=0
### Register the excluded subnets in the routeadd / routedel files
for EXCLUDED_SUBNET in '{{ $removeShortcuts := join "' '" .host.vpn.excludedSubnets }}'; do
ensureNetworkConfigs
nmcli connection modify "$OVPN_NAME" +ipv4.routes "$EXCLUDED_SUBNET" | sudo tee -a /etc/network/if-up.d/routeadd
nmcli connection modify "$OVPN_NAME" -ipv4.routes "$EXCLUDED_SUBNET" | sudo tee -a /etc/network/if-post-down.d/routedel
fi
RESTART_NM=true
done
else
logg info 'Either the OpenVPN username or password is undefined.'
logg info 'See the docs/VARIABLES.md file for details.'
fi
{{ if (stat (joinPath .host.home ".config" "age" "chezmoi.txt")) }}
### Setup WireGuard profiles
if [ -d /etc/NetworkManager/system-connections ]; then
find "${XDG_CONFIG_HOME:-$HOME/.config}/vpn" -type f -name "*.nmconnection" | while read WG_FILE; do
### Ensure the WireGuard NetworkManager plugin is available
if [ ! -d /usr/lib/NetworkManager/nm-wireguard-service ]; then
logg info 'The nm-wireguard-service is not present'
logg info 'Installing the nm-wireguard-service'
fi
### Add the WireGuard profiles
logg info "Adding $WG_FILE to /etc/NetworkManager/system-connections"
WG_FILENAME="$(basename "$WG_FILE")"
chezmoi decrypt "$WG_FILE" | sudo tee "/etc/NetworkManager/system-connections/$WG_FILENAME"
### Register the excluded subnets in the routeadd / routedel files
for EXCLUDED_SUBNET in '{{ $removeShortcuts := join "' '" .host.vpn.excludedSubnets }}'; do
ensureNetworkConfigs
WG_PROFILE_NAME="$(echo "$WG_FILENAME" | sed 's/.nmconnection$//')"
nmcli connection modify "$WG_PROFILE_NAME" +ipv4.routes "$EXCLUDED_SUBNET" | sudo tee -a /etc/network/if-up.d/routeadd
nmcli connection modify "$WG_PROFILE_NAME" -ipv4.routes "$EXCLUDED_SUBNET" | sudo tee -a /etc/network/if-post-down.d/routedel
fi
RESTART_NM=true
done
else
logg warn '/etc/NetworkManager/system-connections is not a directory!'
fi
{{ end -}}
### Restart NetworkManager if changes were made and environment is not WSL
if [ "$RESTART_NM" == 'true' ] && [[ ! "$(test -d proc && grep Microsoft /proc/version > /dev/null)" ]]; then
logg info 'Restarting NetworkManager since VPN profiles were updated'
sudo service NetworkManager restart
fi
else
logg warn 'nmcli is unavailable'
fi
fi
}
# @description
# This script applies the SSH server MOTD banner and `sshd_config` (which are housed in the `home/private_dot_ssh/system` location)
# to the system by copying the files to the system location and then restarting / enabling the system SSH server.
#
# ## Links
#
# * [System SSHD configurations](https://github.com/megabyte-labs/install.doctor/tree/master/home/private_dot_ssh/system)
configureSSHD() {
### Update /etc/ssh/sshd_config if environment is not WSL
if [[ ! "$(test -d /proc && grep Microsoft /proc/version > /dev/null)" ]]; then
if [ -d /etc/ssh ]; then
logg info 'Copying ~/.ssh/system/banner to /etc/ssh/banner' && sudo cp -f "$HOME/.ssh/system/banner" /etc/ssh/banner
logg info 'Copying ~/.ssh/system/sshd_config to /etc/ssh/sshd_config' && sudo cp -f "$HOME/.ssh/system/sshd_config" /etc/ssh/sshd_config
if command -v semanage > /dev/null; then
logg info 'Apply SELinux configuration addressing custom SSH port' && sudo semanage port -a -t ssh_port_t -p tcp {{ .host.ssh.port }}
logg info 'Allow NIS SSHD' && sudo setsebool -P nis_enabled 1
fi
### Ensure keys are created
logg info 'Running sudo ssh-keygen -A' && sudo ssh-keygen -A
### Restart SSH server
if [ -d /Applications ] && [ -d /System ]; then
### macOS
logg info 'Running sudo systemsetup -setremotelogin on' && sudo systemsetup -setremotelogin on
logg info 'Running sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist' && sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist 2> /dev/null
logg info 'Running sudo launchctl stop com.openssh.sshd' && sudo launchctl stop com.openssh.sshd
logg info 'Running sudo launchctl start com.openssh.sshd' && sudo launchctl start com.openssh.sshd && logg info 'Successfully ran launchctl start com.openssh.sshd'
else
### Linux
logg info 'Enabling the sshd service'
sudo systemctl enable sshd
logg info 'Restarting the sshd service'
sudo systemctl restart sshd && logg info 'Successfully ran sudo systemctl restart sshd'
fi
else
logg warn 'The /etc/ssh folder does not exist'
fi
else
logg info 'Skipping sshd_config application since environment is WSL'
fi
}
# @description
# This script allows you to apply `dconf` settings that you can store in your fork of Install Doctor. By default,
# it makes a handful of `dconf` settings optimizations.
dconfSettings() {
if command -v dconf > /dev/null; then
### Update background to be OS-specific
if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/dconf/settings/org.gnome.desktop.background" ]; then
logg info 'Checking for presence of /usr/local/share/wallpapers/Betelgeuse-{{ title .host.distro.id }}/contents/source.jpg'
if [ -f /usr/local/share/wallpapers/Betelgeuse-{{ title .host.distro.id }}/contents/source.jpg ]; then
logg info "Updating ${XDG_CONFIG_HOME:-$HOME/.config}/dconf/settings/org.gnome.desktop.background to point to OS-specific background"
TMP="$(mktemp)"
sed 's/Betelgeuse/Betelgeuse-{{ title .host.distro.id }}/g' < "${XDG_CONFIG_HOME:-$HOME/.config}/dconf/settings/org.gnome.desktop.background" > "$TMP"
mv "$TMP" "${XDG_CONFIG_HOME:-$HOME/.config}/dconf/settings/org.gnome.desktop.background"
else
logg info 'OS-specific background not found'
fi
fi
### Backup system settings
DCONF_TMP="$(mktemp)"
dconf dump / > "$DCONF_TMP"
logg info 'Backed up system dconf settings to '"$DCONF_TMP"
### Reset system settings / load saved configurations from ~/.config/dconf/settings
if [ -d "${XDG_CONFIG_HOME:-$HOME/.config}/dconf/settings" ]; then
find "${XDG_CONFIG_HOME:-$HOME/.config}/dconf/settings" -mindepth 1 -maxdepth 1 -type f | while read DCONF_CONFIG_FILE; do
if [ "$DEBUG_MODE" == 'true' ]; then
logg info 'Dconf configuration file:'
echo "$DCONF_CONFIG_FILE"
fi
DCONF_SETTINGS_ID="/$(basename "$DCONF_CONFIG_FILE" | sed 's/\./\//g')/"
if [ "$DEBUG_MODE" == 'true' ]; then
logg info 'Dconf settings ID:'
echo "$DCONF_SETTINGS_ID"
fi
### Reset dconf settings if environment variable RESET_DCONF is set to true
if [ "$RESET_DCONF" == 'true' ]; then
logg info 'Resetting dconf settings for '"$DCONF_SETTINGS_ID"''
dconf reset -f "$DCONF_SETTINGS_ID"
fi
logg info 'Loading versioned dconf settings for '"$DCONF_SETTINGS_ID"''
dconf load "$DCONF_SETTINGS_ID" < "$DCONF_CONFIG_FILE"
logg success 'Finished applying dconf settings for '"$DCONF_SETTINGS_ID"''
done
else
logg warn '~/.config/dconf/settings does not exist!'
fi
fi
}
# @description
# This script decrypts the SSH key files that are housed in the `home/.chezmoitemplates/ssh` section of the repository.
# It loops through all the files in `home/.chezmoitemplates/ssh` and stores them to the `~/.ssh` folder
# when they are successfully decrypted.
#
# This script generates a pair of default `id_rsa` and `id_rsa.pub` keys if one is not already present
# on the system after the Install Doctor provisioning process completes. It also ensures the private
# key is only readable and writable the provisioning user.
decryptSSHKeys() {
### Unpack existing encrypted keys
logg info 'Decrypting SSH keys stored in the home/.chezmoitemplates/ssh folder of the Install Doctor repo / fork.'
find "{{ .chezmoi.sourceDir }}/.chezmoitemplates/ssh" -type f | while read SSH_FILE; do
### Decrypt SSH file with Chezmoi
logg info "Decrypting the $(basename "$SSH_FILE") encrypted SSH file"
chezmoi decrypt "$SSH_FILE" > "$HOME/.ssh/$(basename "$SSH_FILE")" || EXIT_CODE=$?
### Handle failed decryption with warning log message
if [ -n "$EXIT_CODE" ]; then
logg warn "Unable to decrypt the file stored in $SSH_FILE"
fi
### Apply appropriate permission to decrypted ~/.ssh file
if [ -f "$HOME/.ssh/$(basename "$SSH_FILE")" ]; then
logg info "Applying appropriate permissions on $HOME/.ssh/$(basename "$SSH_FILE")"
chmod 600 "$HOME/.ssh/$(basename "$SSH_FILE")"
fi
done
### Ensure id_rsa is present and create one if it does not exist
if [ ! -f "$HOME/.ssh/id_rsa" ]; then
logg 'Generating missing default private key / public key (~/.ssh/id_rsa)'
ssh-keygen -b 4096 -t rsa -f "$HOME/.ssh/id_rsa" -q -N ""
chmod 600 "$HOME/.ssh/id_rsa"
fi
}
# @description
# This script installs and activates the latest version of Emscripten. This script
# implements the [instructions outlined on Emscripten's website](https://emscripten.org/docs/getting_started/downloads.html#installation-instructions-using-the-emsdk-recommended).
#
# This script will only run when `${XDG_DATA_HOME:-$HOME/.local/share}/emsdk` is present on the system. This folder
# is populated via the definition in `home/.chezmoiexternal.toml.tmpl`.
emscriptenInstall() {
if [ -d "${XDG_DATA_HOME:-$HOME/.local/share}/emsdk" ]; then
logg info 'Pulling latest changes for Emscripten source code' && cd "${XDG_DATA_HOME:-$HOME/.local/share}/emsdk" && git pull
logg info "Running emsdk install latest" && emsdk install latest > /dev/null
logg info "Running emsdk activate latest" && emsdk activate latest > /dev/null
logg info 'Profile source inclusions are already implemented in Bash / ZSH profile'
fi
}
# @description
# Adds auto-update feature to macOS that automatically downloads and installs updates. Also enables
# an auto-update feature for Homebrew on macOS.
enableAutoUpdateDarwin() {
if [ -d /Applications ] && [ -d /System ]; then
### Enable automated system updates on macOS
if [ -f "$HOME/Library/LaunchDaemons/com.apple.automatedupdates.plist" ] && [ ! -f "/Library/LaunchDaemons/com.apple.automatedupdates.plist" ]; then
logg info 'Configuring macOS to automatically apply system updates'
sudo mkdir -p /Library/LaunchDaemons
sudo cp -f "$HOME/Library/LaunchDaemons/com.apple.automatedupdates.plist" "/Library/LaunchDaemons/com.apple.automatedupdates.plist"
logg info 'Loading /Library/LaunchDaemons/com.apple.automatedupdates.plist'
sudo launchctl load "/Library/LaunchDaemons/com.apple.automatedupdates.plist" && logg success 'launchctl load successful'
fi
### Enable Homebrew auto-update service
if brew autoupdate status | grep 'Autoupdate is not configured.' > /dev/null; then
logg info 'Enabling Homebrew auto-update service (every 24 hours)'
brew autoupdate start --cleanup --greedy --upgrade
fi
fi
}
# @description
# This script modifies the `/etc/environment` file on Linux devices to include:
#
# * `export QT_STYLE_OVERRIDE=kvantum-dark` which is required for the Linux GNOME / KDE themeing that relies on Kvantum.
ensureQtStyleOverride() {
### Ensure QT_STYLE_OVERRIDE is set in /etc/environment
logg info 'Ensuring QT_STYLE_OVERRIDE is set in /etc/environment'
if cat /etc/environment | grep QT_STYLE_OVERRIDE > /dev/null; then
sudo sed -i 's/.*QT_STYLE_OVERRIDE.*/export QT_STYLE_OVERRIDE=kvantum-dark/' /etc/environment
logg info 'Updated QT_STYLE_OVERRIDE in /etc/environment'
else
echo 'export QT_STYLE_OVERRIDE=kvantum-dark' | sudo tee -a /etc/environment
logg info 'Added QT_STYLE_OVERRIDE to /etc/environment'
fi
}
# @description Run `gem update --system` if `gem` is available
ensureSystemGemUpdated() {
### Ensure gem is updated
if command -v gem > /dev/null; then
logg info 'Ensuring system gem is updated' && gem update --system
else
logg info 'Could not find gem in PATH so skipping gem system update'
fi
}
# @description
# This script ensures ZSH is used as the default shell by ensuring it is added to `/etc/shells`. The script
# also ensures ZSH is available at `/usr/local/bin/zsh` on ARM64 systems by symlinking the Homebrew ZSH shell
# to `/usr/local/bin/zsh` if it is missing.
ensureZshShell() {
logg 'Ensuring ZSH is set as the default shell'
if ! grep -qc "/usr/local/bin/zsh" /etc/shells; then
echo "/usr/local/bin/zsh" | sudo tee -a /etc/shells > /dev/null
fi
if [ ! -f /usr/local/bin/zsh ] && [ -f "${HOMEBREW_PREFIX:-/opt/homebrew}/bin/zsh" ]; then
sudo ln -sf "${HOMEBREW_PREFIX:-/opt/homebrew}/bin/zsh" /usr/local/bin/zsh
fi
}
# @description
# This script ensures your GNOME extensions come pre-configured to your liking. It provides the ability
# to automatically install a configurable list of GNOME extensions as well as apply your preferred settings.
gnomeExtensionSettings() {
if command -v gnome-shell > /dev/null; then
### Ensure /tmp/install-gnome-extensions.txt is not present on the system
if [ -f /tmp/install-gnome-extensions.txt ]; then
rm -f /tmp/install-gnome-extensions.txt
fi
### Register temporary file for gnome.yml JSON
if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/desktop/gnome.yml" ]; then
TMP_YQ="$(mktemp)"
cat "${XDG_CONFIG_HOME:-$HOME/.config}/desktop/gnome.yml" | yq e -o=j '.' > "$TMP_YQ"
fi
### Populate /tmp/install-gnome-extensions.txt with GNOME extensions that need to be installed
if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/desktop/gnome.yml" ]; then
cat "$TMP_YQ" | jq -c '.default_gnome_extensions[] | tojson' | while read EXT; do
TMP="$(mktemp)"
echo "$EXT" | sed 's/^.\(.*\).$/\1/' > "$TMP"
EXT_URL="$(cat "$TMP" | jq -r '.url')"
EXT_ID="$(cat "$TMP" | jq -r '.regex')"
echo "$EXT_URL" >> /tmp/install-gnome-extensions.txt
if [ ! -d "${XDG_DATA_HOME:-$HOME/.local/share}/gnome-shell/extensions" ]; then
mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/gnome-shell/extensions"
fi
find "${XDG_DATA_HOME:-$HOME/.local/share}/gnome-shell/extensions" -mindepth 1 -maxdepth 1 -type d | while read EXT_FOLDER; do
if [[ "$EXT_FOLDER" == *"$EXT_ID"* ]] && [ -f /tmp/install-gnome-extensions.txt ]; then
TMP_EXT="$(mktemp)"
head -n -1 /tmp/install-gnome-extensions.txt > "$TMP_EXT"
mv -f "$TMP_EXT" /tmp/install-gnome-extensions.txt > /dev/null
fi
done
done
else
logg warn 'The ~/.config/desktop/gnome.yml file is missing so GNOME extension install orders cannot be calculated'
fi
### Remove /tmp/install-gnome-extensions.txt if it is empty
if [ "$(cat /tmp/install-gnome-extensions.txt)" == "" ]; then
rm -f /tmp/install-gnome-extensions.txt > /dev/null
fi
### Install the GNOME extensions using the `install-gnome-extensions` script
if command -v install-gnome-extensions > /dev/null; then
if [ -f /tmp/install-gnome-extensions.txt ]; then
logg info 'Running the install-gnome-extensions script'
cd /tmp
install-gnome-extensions --enable --overwrite --file /tmp/install-gnome-extensions.txt
rm -f /tmp/install-gnome-extensions.txt
logg success 'Finished installing the GNOME extensions'
else
logg info 'No new GNOME extensions to install'
fi
else
logg warn 'Cannot install GNOME extensions because the install-gnome-extensions script is missing from ~/.local/bin'
fi
### Apply plugin gsettings
if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/desktop/gnome.yml" ]; then
cat "$TMP_YQ" | jq -c '.default_gnome_extensions[] | tojson' | while read EXT; do
if [ "$DEBUG_MODE" == 'true' ]; then
logg info 'Extension data:'
echo "$EXT"
fi
TMP="$(mktemp)"
echo "$EXT" | sed 's/^.\(.*\).$/\1/' > "$TMP"
EXT_URL="$(cat "$TMP" | jq -r '.url')"
EXT_ID="$(cat "$TMP" | jq -r '.regex')"
if [ "$DEBUG_MODE" == 'true' ]; then
logg info 'Extension ID:'
echo "$EXT_ID"
fi
EXT_SETTINGS_TYPE="$(cat "$TMP" | jq -r '.settings | type')"
EXT_SETTINGS="$(cat "$TMP" | jq -r '.settings')"
if [ "$EXT_SETTINGS" != 'null' ]; then
logg info 'Evaluating extension settings for '"$EXT_ID"''
if [ "$EXT_SETTINGS_TYPE" == 'array' ]; then
cat "$TMP" | jq -r '.settings[]' | while read EXT_SETTING; do
logg info 'Applying following extension setting:'
echo "$EXT_SETTING"
eval "$EXT_SETTING"
done
else
logg info 'Applying following extension setting:'
echo "$EXT_SETTINGS"
eval "$EXT_SETTINGS"
fi
logg success 'Applied gsettings configuration for the '"$EXT_ID"' GNOME extension'
fi
done
fi
fi
}
# @description
# This script configures GRUB2 with a custom theme on Linux systems.
grubSettings() {
if [ "{{ .host.distro.family }}" = "linux" ]; then
### Fix Qubes issue
if command -v qubesctl > /dev/null && [ -f /boot/grub2/grubenv ] && [ -d /boot/efi/EFI/qubes ]; then
sudo cp -f /boot/grub2/grubenv /boot/efi/EFI/qubes/grubenv
logg success 'Copied /boot/grub2/grubenv to /boot/efi/EFI/qubes/grubenv'
fi
### Ensure /boot/grub2/themes is directory
if [ ! -d /boot/grub2/themes ]; then
sudo mkdir -p /boot/grub2/themes
logg success 'Created /boot/grub2/themes'
fi
### Copy GRUB theme to /boot/grub2/themes
if [ -d /usr/local/share/grub/themes ]; then
sudo cp -rf /usr/local/share/grub/themes /boot/grub2/
logg success 'Copied GRUB themes in /usr/local/share/grub/themes to /boot/grub2/themes'
else
logg warn '/usr/local/share/grub/themes is missing'
fi
### Set default GRUB screen resolution variables
SCREEN_RATIO_ULTRAWIDE="2100"
GRUB_RESOLUTION_TYPE="1080p"
### Determine screen size ratio (used for picking GRUB2 theme resolution)
if command -v xrandr > /dev/null && command -v uniq > /dev/null; then
SCREEN_WIDTH="$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f1)"
SCREEN_HEIGHT="$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2)"
SCREEN_RATIO="$(awk -v height="$SCREEN_HEIGHT" -v width="$SCREEN_WIDTH" 'BEGIN { print ((height / width) * 1000) }')"
SCREEN_RATIO="${SCREEN_RATIO%.*}"
logg success "Screen detected as $SCREEN_WIDTH x $SCREEN_HEIGHT (ratio of $SCREEN_RATIO)"
if (( $(echo "$SCREEN_RATIO $SCREEN_RATIO_ULTRAWIDE" | awk '{print ($1 > $2)}') )); then
GRUB_RESOLUTION_TYPE="ultrawide"
logg info 'GRUB resolution registered as ultrawide'
fi
else
logg warn 'Missing either xrandr or uniq (required for calculating screen size ratio)'
fi
### Optimize the GRUB resolution
if [ -f /etc/default/grub ]; then
### GRUB_GFXMODE
logg info 'Setting GRUB_GFXMODE=auto in /etc/default/grub'
if cat /etc/default/grub | grep GRUB_GFX_MODE > /dev/null; then
sudo sed -i 's/.*GRUB_GFXMODE.*/GRUB_GFXMODE=auto/' /etc/default/grub
else
echo "GRUB_GFXMODE=auto" | sudo tee -a /etc/default/grub > /dev/null
fi
### GRUB_GFXPAYLOAD_LINUX
logg info 'Setting GRUB_GFXPAYLOAD_LINUX=keep in /etc/default/grub'
if cat /etc/default/grub | grep GRUB_GFXPAYLOAD_LINUX > /dev/null; then
sudo sed -i 's/.*GRUB_GFXPAYLOAD_LINUX.*/GRUB_GFXPAYLOAD_LINUX="keep"/' /etc/default/grub
else
echo 'GRUB_GFXPAYLOAD_LINUX="keep"' | sudo tee -a /etc/default/grub > /dev/null
fi
### GRUB_THEME
logg info 'Setting GRUB_THEME={{ .theme }} in /etc/default/grub'
if cat /etc/default/grub | grep GRUB_THEME > /dev/null; then
sudo sed -i 's/.*GRUB_THEME.*/GRUB_THEME="{{ .theme }}-'"$GRUB_RESOLUTION_TYPE"'"/' /etc/default/grub
else
echo 'GRUB_THEME="{{ .theme }}-'"$GRUB_RESOLUTION_TYPE"'"' | sudo tee -a /etc/default/grub > /dev/null
fi
### GRUB_BACKGROUND
# Removed since the background should be flat black which is configurable
# Leaving this code here in case we need to add a flat black image background for some reason
# logg info 'Setting GRUB_BACKGROUND=/usr/local/share/grub/{{ .theme }}-blue.png in /etc/default/grub'
# if cat /etc/default/grub | grep GRUB_BACKGROUND > /dev/null; then
# sudo sed -i 's/.*GRUB_BACKGROUND.*/GRUB_BACKGROUND="\/usr\/local\/share\/grub\/{{ .theme }}-blue.png"/' /etc/default/grub
# else
# echo 'GRUB_BACKGROUND="/usr/local/share/grub/{{ .theme }}-blue.png"' | sudo tee -a /etc/default/grub > /dev/null
# fi
### GRUB_TIMEOUT
logg info 'Setting GRUB_TIMEOUT={{ .grub.timeout }} in /etc/default/grub'
if cat /etc/default/grub | grep GRUB_TIMEOUT > /dev/null; then
sudo sed -i 's/.*GRUB_TIMEOUT.*/GRUB_TIMEOUT="{{ .grub.timeout }}"/' /etc/default/grub
else
echo 'GRUB_TIMEOUT="{{ .grub.timeout }}"' | sudo tee -a /etc/default/grub > /dev/null
fi
### GRUB_FORCE_HIDDEN_MENU
logg info 'Setting GRUB_FORCE_HIDDEN_MENU={{ .grub.shiftToSee }} in /etc/default/grub'
sudo sed -i '/GRUB_FORCE_HIDDEN_MENU/d' /etc/default/grub
echo "GRUB_FORCE_HIDDEN_MENU={{ .grub.shiftToSee }}" | sudo tee -a /etc/default/grub > /dev/null
### Remove duplicate lines in /etc/default/grub
logg info 'Ensuring there are no duplicate entries in /etc/default/grub'
cat /etc/default/grub | uniq | sudo tee /etc/default/grub > /dev/null
else
logg warn '/etc/default/grub is missing'
fi
### Determine platform-specific icon to use
if command -v qubesctl > /dev/null; then
GRUB_ICON='qubes'
elif [ -f "/usr/local/share/grub/themes/{{ .theme }}-$GRUB_RESOLUTION_TYPE/icons/{{ .host.distro.id }}.png" ]; then
GRUB_ICON='{{ .host.distro.id }}'
elif [ -f "/usr/local/share/grub/themes/{{ .theme }}-$GRUB_RESOLUTION_TYPE/icons/{{ .host.distro.family }}.png" ]; then
GRUB_ICON='{{ .host.distro.family }}'
else
GRUB_ICON='linux'
fi
### Copy icon to GRUB2 theme folder
# Check looks in /usr/local/share/grub because on some systems the /boot folder is behind permissions for non-root users
if [ -f "/usr/local/share/grub/themes/{{ .theme }}-$GRUB_RESOLUTION_TYPE/icons/$GRUB_ICON.png" ]; then
sudo cp -f /boot/grub2/themes/{{ .theme }}-$GRUB_RESOLUTION_TYPE/icons/$GRUB_ICON.png /boot/grub2/themes/{{ .theme }}-$GRUB_RESOLUTION_TYPE/icon.png
logg success 'Copied platform-specific icon to GRUB2 theme folder'
else
logg warn "/boot/grub2/themes/{{ .theme }}-$GRUB_RESOLUTION_TYPE/icons/$GRUB_ICON.png is missing"
fi
### Hide unnecessary Boot messages and Bliking cursor
GRUB_DEFAULT_CMDLINE=$(grep 'GRUB_CMDLINE_LINUX_DEFAULT' /etc/default/grub)
if [[ -n $GRUB_DEFAULT_CMDLINE ]]; then
KERNEL_PARAMS_QUIET=$(echo "$GRUB_DEFAULT_CMDLINE" | grep 'quiet')
logg info 'Updating GRUB_CMDLINE_LINUX_DEFAULT to hide log messages'
if [[ -z $KERNEL_PARAMS_QUIET ]]; then
sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=3 systemd.show_status=auto rd.udev.log_level=3 vt.global_cursor_default=0 /' /etc/default/grub
else
NEW_KERNEL_PARAMS=$(echo $KERNEL_PARAMS_QUIET | sed -e "s/quiet/quiet loglevel=3 systemd.show_status=auto rd.udev.log_level=3 vt.global_cursor_default=0/")
sudo sed -i "s/^GRUB_CMDLINE_LINUX_DEFAULT.*/${NEW_KERNEL_PARAMS}/" /etc/default/grub
fi
else
logg info 'GRUB_CMDLINE_LINUX_DEFAULT was not present, adding one with parameters to hide log messages'
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=3 systemd.show_status=auto rd.udev.log_level=3 vt.global_cursor_default=0"' | sudo tee -a /etc/default/grub > /dev/null
fi
### Ensure grub2-mkconfig is available
if ! command -v grub2-mkconfig > /dev/null; then
if command -v grub-mkconfig > /dev/null; then
sudo ln -s "$(which grub-mkconfig)" /usr/bin/grub2-mkconfig
elif sudo which grub-mkconfig > /dev/null; then
sudo ln -s "$(sudo which grub-mkconfig)" /usr/bin/grub2-mkconfig
else
logg warn 'Neither grub2-mkconfig or grub-mkconfig are available'
fi
fi
### Apply GRUB2 theme
# Set export DEBUG_MODE=true to bypass GRUB2 / Plymouth application
if [ "$DEBUG_MODE" != 'true' ]; then
if command -v grub2-mkconfig > /dev/null; then
if [ -d /sys/firmware/efi ]; then
logg info 'Assuming system is UEFI-enabled since /sys/firmware/efi is present'
if [ -f /boot/efi/EFI/qubes/grub.cfg ]; then
logg info 'Running sudo grub2-mkconfig -o /boot/efi/EFI/qubes/grub.cfg'
sudo grub2-mkconfig -o /boot/efi/EFI/qubes/grub.cfg
logg success 'Applied GRUB2 theme'
elif [ -f /boot/efi/EFI/grub.cfg ]; then
logg info 'Running sudo grub2-mkconfig -o /boot/efi/EFI/grub.cfg'
sudo grub2-mkconfig -o /boot/efi/EFI/grub.cfg
logg success 'Applied GRUB2 theme'
else
logg warn 'Unknown GRUB2 configuration - not applying GRUB2 theme'
fi
else
logg info 'Assuming system is non-UEFI since /sys/firmware/efi is not present'
logg info 'Running sudo grub2-mkconfig -o /boot/grub2/grub.cfg'
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
logg success 'Applied GRUB2 theme'
fi
elif [ -f /usr/sbin/update-grub ]; then
logg info 'Running sudo update-grub'
sudo update-grub
else
logg warn 'Unable to find appropriate GRUB mkconfig command'
fi
else
logg info 'Skipping GRUB2 theme application because DEBUG_MODE is set to true'
fi
fi
}
# @description
# This script sets the [Docker Rclone plugin](https://rclone.org/docker/) which allows you to mount Rclone mounts as Docker volumes
#
# ## Docker Rclone
#
# The Docker Rclone installation ensures necessary system directories are initialized / created. It also copies the [Docker Rclone configuration](https://github.com/megabyte-labs/install.doctor/blob/master/home/dot_config/rclone/private_docker-rclone.conf.tmpl)
# to the proper system location.
installDockerRclonePlugin() {
### Docker Rclone plugin
# Source: https://rclone.org/docker/
# First, ensure Docker Rclone configuration exists (which only happens when the Chezmoi Age decryption key is present as well as keys for Rclone)
if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/rclone/docker-rclone.conf" ]; then
### Ensure Docker Rclone plugin system folders exist
logg info 'Ensure Docker Rclone plugin system folders exist'
logg info 'Ensuring directory /var/lib/docker-plugins/rclone/config is created' && sudo mkdir -p /var/lib/docker-plugins/rclone/config
logg info 'Ensuring directory /var/lib/docker-plugins/rclone/cache is created' && sudo mkdir -p /var/lib/docker-plugins/rclone/cache
### Copy Rclone configuration
logg info "Copy the Rclone configuration from ${XDG_CONFIG_HOME:-$HOME/.config}/rclone/docker-rclone.conf to /var/lib/docker-plugins/rclone/config/rclone.conf"
sudo cp -f "${XDG_CONFIG_HOME:-$HOME/.config}/rclone/docker-rclone.conf" /var/lib/docker-plugins/rclone/config/rclone.conf
### Install the Rclone Docker plugin (if not already installed)
if ! sudo su -c 'docker plugin ls' - "$USER" | grep 'rclone:latest' > /dev/null; then
sudo su -c 'docker plugin install rclone/docker-volume-rclone:amd64 args="-v" --alias rclone --grant-all-permissions' - "$USER"
fi
fi
}
# @description
# This script loads crontab jobs that are defined and housed in your Install Doctor fork.
loadCronjobs() {
logg info 'Installing user crontab jobs'
crontab < "${XDG_CONFIG_HOME:-$HOME/.config}/crontab/config-user" || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
logg error 'Failed to load cronjobs for user'
fi
logg info 'Installing system crontab jobs'
sudo crontab < "${XDG_CONFIG_HOME:-$HOME/.config}/crontab/config-system" || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
logg error 'Failed to load cronjobs for system'
fi
}
# @description
# This script houses a wide range of macOS system tweaks that are intended to improve the developer experience on macOS,
# as well as improve security. Some of the tweaks include modifying default settings for various applications.
macOSSettings() {
if [ -d /System ] && [ -d /Applications ] && [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/shell/macos.sh" ]; then
bash "${XDG_CONFIG_HOME:-$HOME/.config}/shell/macos.sh"
fi
}
# @description Ensures all files in `~/.local/bin` are executable
makeLocalBinExecutable() {
logg info 'Ensuring all files in ~/.local/bin are executable'
find "$HOME/.local/bin" -mindepth 1 -maxdepth 2 -type f | while read EXE_FILE; do
chmod +x "$EXE_FILE"
done
}
# @description
# Install Doctor was previously called Gas Station. It was also Ansible based. Some of the features that Install Doctor
# provides are made available via Ansible roles that Gas Station provides. This script symlinks Gas Station's roles
# so that they can be leveraged by Install Doctor.
#
# Some of the roles that Gas Station provides are not available via Ansible Galaxy yet. This script symlinks Gas Station
# roles to an Ansible Galaxy / Ansible friendly location.
#
# ## Ansible Installation
#
# If Ansible is not already installed, this script will also install Ansible and all the necessary requirements using `pipx`.
# This script must run before the `install-packages` script because some of the Ansible roles might be leveraged by it.
#
# ## TODO
#
# * Move installation logic into the ZX installer so that Ansible and its dependencies are only installed when required
# * Remove Ansible dependency completely
symlinkAnsibleRoles() {
logg info 'Ensuring Gas Station roles are symlinked to ~/.local/share/ansible/roles'
mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles"
find "${XDG_DATA_HOME:-$HOME/.local/share}/gas-station/roles" -mindepth 2 -maxdepth 2 -type d | while read ROLE_PATH; do
ROLE_FOLDER="professormanhattan.$(echo "$ROLE_PATH" | sed 's/.*\/\([^\/]*\)$/\1/')"
ALT_ROLE_FOLDER="$(echo "$ROLE_PATH" | sed 's/.*\/\([^\/]*\)$/\1/')"
if [ ! -d "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ROLE_FOLDER" ] || [ "$(readlink -f "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ROLE_FOLDER")" != "$ROLE_PATH" ]; then
logg info 'Symlinking '"$ROLE_FOLDER"''
rm -f "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ROLE_FOLDER"
ln -s "$ROLE_PATH" "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ROLE_FOLDER"
fi
if [ ! -d "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ALT_ROLE_FOLDER" ] || [ "$(readlink -f "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ALT_ROLE_FOLDER")" != "$ROLE_PATH" ]; then
rm -f "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ALT_ROLE_FOLDER"
ln -s "$ROLE_PATH" "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/roles/$ALT_ROLE_FOLDER"
fi
done
if [ -f "${XDG_DATA_HOME:-$HOME/.local/share}/gas-station/requirements.yml" ]; then
### Install Ansible Galaxy and dependencies if missing
if ! command -v ansible-galaxy > /dev/null; then
if ! command -v pipx > /dev/null; then
logg info 'Installing pipx via Homebrew'
brew install --quiet pipx
logg info 'Running pipx ensurepath'
pipx ensurepath
fi
logg info 'Installing ansible-core via pipx'
pipx install ansible-core
if [ -d /Applications ] && [ -d /System ]; then
logg info 'Injecting macOS-specific pipx dependencies via pipx'
pipx inject ansible-core PyObjC PyObjC-core
fi
logg info 'Injecting Ansible dependencies via pipx'
pipx inject ansible-core docker lxml netaddr pexpect python-vagrant pywinrm requests-credssp watchdog
mkdir -p "${XDG_CACHE_HOME:-$HOME/.cache}/megabyte-labs"
touch "${XDG_CACHE_HOME:-$HOME/.cache}/megabyte-labs/ansible-installed"
fi
### Ensure Ansible Galaxy was successfully loaded and then install the Ansible Galaxy requirements
if command -v ansible-galaxy > /dev/null; then
logg info 'Ensuring Ansible Galaxy collections are installed'
export ANSIBLE_CONFIG="${XDG_DATA_HOME:-$HOME/.local/share}/ansible/ansible.cfg"
ansible-galaxy install -r "${XDG_DATA_HOME:-$HOME/.local/share}/ansible/requirements.yml" > /dev/null || EXIT_CODE=$?
if [ -n "$EXIT_CODE" ]; then
logg error 'Failed to install Ansible requirements from Ansible Galaxy'
if [ -d "${XDG_DATA_HOME:-$HOME/.local/share}/gas-station/collections" ]; then
logg info 'Attempting to use locally stored Ansible requirements'
cd "${XDG_DATA_HOME:-$HOME/.local/share}/gas-station/collections"
ansible-galaxy install -r requirements.yml || SECOND_EXIT_CODE=$?
if [ -n "$SECOND_EXIT_CODE" ]; then
logg error 'Failed to install requirements from both the cloud and the local copy' && exit 1
fi
else
logg warn "${XDG_DATA_HOME:-$HOME/.local/share}/gas-station/collections is missing"
fi
fi
else
logg warn 'Unable to install the Ansible Galaxy requirements.yml since the ansible-galaxy executable is missing from the PATH'
fi
else
logg warn '~/.local/share/ansible/requirements.yml is missing'
fi
}
# @description
# This script checks if `python3` is available and if `python` is not available. If both are true, then the script
# symlinks `python` to `python3` so that the `python` command uses `python3`.
#
# This is useful if you do not want to install Python 2.7 and would like Python 3 to be used in all scenarios where Python is
# invoked with the `python` command.
symlinkPython() {
### Symlink python3 to python if it is unavailable
if ! command -v python > /dev/null && command -v python3 > /dev/null; then
logg info 'Symlinking python3 to python since the latter is unavailable'
sudo ln -s "$(which python3)" /usr/local/bin/python
fi
}
# @description
# This script creates an empty directory with each user's name in `/var/log/user`. It initializes the folder in hopes
# that we can eventually store all user logs in a single directory alongside the system logs folder.
userLogFolders() {
find '{{ .host.homeParentFolder }}' -mindepth 1 -maxdepth 1 -type d | while read HOME_DIR; do
USER_FOLDER="$(echo "$HOME_DIR" | sed 's/.*\/\([^\/]*\)$/\1/')"
if [ -d "$HOME_DIR/.local" ]; then
if [ ! -d "/var/log/user/$USER_FOLDER" ]; then
logg info 'Creating /var/log/user/'"$USER_FOLDER"'' && sudo mkdir -p "/var/log/user/$USER_FOLDER"
fi
logg info "Applying user permissions to /var/log/user/$USER_FOLDER" && sudo chown -Rf "$USER_FOLDER" "/var/log/user/$USER_FOLDER"
fi
done
}
addZshEnv &
applyFontsToSystem &
applyLinuxConfSettings &
applyLinuxThemeFiles &
applyRootConfig &
applyWallpaper &
asdfPluginInstall &
configureNetworkManagerVPNProfiles &
configureSSHD &
dconfSettings &
decryptSSHKeys &
emscriptenInstall &
enableAutoUpdateDarwin &
ensureQtStyleOverride &
ensureSystemGemUpdated &
ensureZshShell &
gnomeExtensionSettings &
grubSettings &
installDockerRclonePlugin &
loadCronjobs &
macOSSettings &
makeLocalBinExecutable &
symlinkAnsibleRoles &
symlinkPython &
userLogFolders &
wait
logg info 'Completed asynchronous post-dotfile-application routine'