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