#!/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'