#!/bin/bash # shellcheck shell=bash { # curybrackets is included to ensure everything is downloaded #set -e # if undefined, use $HOME/.local if [[ $PREFIX == "" ]]; then export PREFIX="$HOME/.local" [[ ! -d "$PREFIX/bin" ]] && mkdir "$PREFIX/bin" -p fi # TODO: Make this better banner() { echoStageNameAdd "Dotfiles Bootstrap Script by Andrei Jiroh" echoStageName "Linux machine bootstrapper starts in 3 seconds..." sleep 3 } useColor() { RED=$(printf '\033[31m') GREEN=$(printf '\033[32m') YELLOW=$(printf '\033[33m') BLUE=$(printf '\033[34m') MAGENTA=$(printf '\033[35m') BOLD=$(printf '\033[1m') RESET=$(printf '\033[m') } echoStageName() { echo "${BOLD}----> $* ${RESET}" } echoStageNameAdd() { echo "${BOLD} $* ${RESET}" } warn() { echo "${YELLOW}warning: $* ${RESET}" } error() { # this will be long, so I must do "&& exit 1" manually echo "${RED}error: $* ${RESET}" } success() { echo "${GREEN}success: $* ${RESET}" } checkOs() { # This step is required for different actions, like installing deps from system-wide package managers # among other sorts of shitfuckery if echo $OSTYPE | grep -qE "linux-android.*"; then export DOTFILES_OS_NAME=android-termux elif echo $OSTYPE | grep -qE '^linux-gnu.*' && [ -f '/etc/debian_version' ]; then # Since Ubuntu is an major Debian fork, they're both LSB-complaint, so # we might need to just use grep for this one in the future. export DOTFILES_OS_NAME=debian-ubuntu if [ -d '/google/devshell' ] && [ -f '/google/devshell/bashrc.google' ]; then export GOOGLE_CLOUD_SHELL=true fi # TODO: Write stuff for Arch users and macOS. In case of WSL, the existence of /wsl.conf may be included in the future. else error "Script unsupported for this machine. See the online README for guide on manual bootstrapping." && exit 1 fi } installDeps() { echoStageName "Installating essiential dependencies" if [[ $DOTFILES_OS_NAME == "android-termux" ]] && [[ $SKIP_DEPENDENCY_INSTALL == "" ]]; then pkg install -y man git nano gnupg openssh proot resolv-conf asciinema openssl-tool echo "info: Essientials are installed, if you need Node.js just do 'pkg install nodejs' (we recommend installing the LTS one for stability) anytime" elif [[ $DOTFILES_OS_NAME == "debian-ubuntu" ]] && [[ $SKIP_DEPENDENCY_INSTALL == "" ]]; then sudo apt install gnupg git nano -y if [[ $USE_PYENV != "" ]]; then # we'll use the pyenv stuff PYENV_ROOT=${PYENV_ROOT:-"$HOME/.pyenv"} if [[ ! -d "${HOME}/.pyenv" ]]; then echoStageName "Installing Pyenv with pyenv-installer" curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash else git -C "${PYENV_ROOT}" pull origin --verbose git -C "${PYENV_ROOT}/plugins/pyenv-doctor" pull origin --verbose git -C "${PYENV_ROOT}/plugins/pyenv-installer" pull origin --verbose git -C "${PYENV_ROOT}/plugins/pyenv-update" pull origin --verbose git -C "${PYENV_ROOT}/plugins/pyenv-virtualenv" pull origin --verbose git -C "${PYENV_ROOT}/plugins/pyenv-which-ext" pull origin --verbose fi echoStageName "Installing build deps as needed by pyenv" sudo apt-get update; sudo apt-get install make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev -y echoStageName "Installing Python3 through Pyenv" "${PYENV_ROOT}/bin/pyenv" install 3.9.6 "${PYENV_ROOT}/bin/pyenv" global 3.9.6 elif [[ $UPDATE_SYSTEM_PYTHON_INSTALL != "" ]]; then echoStageName "Updating Python install" sudo apt install python3 python3-pip --yes fi else warn "Dependency installs are being skipped" fi sleep 5 } installNodeVerManager() { echoStagName "Installing Node.js Version Manager" $(command -v curl >>/dev/null && echo "curl -o-" || echo "wget -qO-") https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | NODE_VERSION=${NODE_VERSION:"lts/*"} NVM_DIR="$HOME/.nvm" PROFILE=/dev/null bash } userspcaeBinDirCheck() { echoStageName "checking if PREFIX/bin exists" if [ ! -d "$PREFIX/bin" ]; then warn "$PREFIX/bin doesn't exists, creating..." mkdir -p "$PREFIX/bin" else success "Looks good! $PREFIX/bin directory exists." fi } cloneRepo() { if [ ! -d "$HOME/.dotfiles" ]; then echoStageName "Cloning the dotfiles repo" git clone https://github.com/ajhalili2006/dotfiles.git $HOME/.dotfiles else echoStageName "Dotfiles repo found, pulling remote changes instead" git -C "$HOME/.dotfiles" fetch --all git -C "$HOME/.dotfiles" pull origin fi sleep 5 } # Decouple secrets repo cloning process from the main cloneSecretsRepo() { # Since I also have an GitHub mirror of that private repo, maybe we can set an variable for that if [[ $USE_GH_SECRETS_MIRROR != "" ]]; then true # just an bypass command to avoid these steps below elif [[ $GITLAB_TOKEN == "" ]] && [[ $GITLAB_LOGIN == "" ]] && [ ! -d "$HOME/.dotfiles/secrets" ]; then error "GitLab login and token can't be blank!" && exit 1 # Probably change my GitLab SaaS username with yours elif [[ $GITLAB_LOGIN != "ajhalili2006" ]] && [ ! -d "$HOME/.dotfiles/secrets" ]; then error "Only Andrei Jiroh can do this!" && exit 1 elif [[ $GITLAB_LOGIN == "ajhalili2006" ]] && [[ $GITLAB_TOKEN == "" ]] && [ ! -d "$HOME/.dotfiles/secrets" ]; then error "Missing GitLab SaaS PAT! Check your Bitwarden vault for that PAT with atleast read_repository scope, or use GitHub mirror instead." && exit 1 fi if [ ! -d "$HOME/.dotfiles/secrets" ]; then echoStageName "Cloning secrets repo" if [[ $USE_GH_SECRETS_MIRROR != "" ]]; then gh repo clone ajhalili2006/dotfiles-secrets elif ! git clone https://$GITLAB_LOGIN:$GITLAB_TOKEN@gitlab.com/ajhalili2006/dotfiles-secrets $HOME/.dotfiles/secrets; then echo "error: That kinda sus, but either only Andrei Jiroh can proceed or maybe the PAT you used is invalid. Probably try to use GitHub mirror instead" && exit 1 fi chmod 760 $HOME/.dotfiles/secrets git -C "$HOME/.dotfiles/secrets" remote set-url origin git@gitlab.com:ajhalili2006/dotfiles-secrets else chmod 760 $HOME/.dotfiles/secrets git -C "$HOME/.dotfiles/secrets" fetch --all git -C "$HOME/.dotfiles/secrets" pull fi sleep 5 } # Install GitHub CLI if we're gonna use that GitHub mirror ghCli() { if [[ $DOTFILES_OS_NAME == "debian-ubuntu" ]] && [[ "$(command -v gh)" == "" ]]; then curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null sudo apt update && sudo apt install gh fi } cleanup() { echoStageName "Bootstrapper successfully ran, cleaning up to ensure no secrets are leaked on env vars..." # just add chaos to these secrets to avoid leaks export GITLAB_LOGIN=gildedguy export GITLAB_TOKEN=build-guid-sus-among-computers-moment if echo "$OSTYPE" | grep -qE "linux-android.*"; then rm -rfv ~/{shellcheck,flarectl,LICENSE,README.txt,README.md} pkg uninstall clang --yes && apt autoremove --yes else rm -rfv ~/{shellcheck,flarectl,LICENSE,README.txt,README.md}* || true unset PREFIX fi success "Setting up a new Linux machine was succesfully executed. To ensure no secrets are leaked when logging utfrom shell session, please do 'history -c' to cleanup shell history." [[ $GOOGLE_CLOUD_SHELL == "true" ]] && warn "Looks like you're on Google Cloud Shell, please restart your virtual machine for changes to take effect." sleep 2 unset DOTFILES_OS_NAME GOOGLE_CLOUD_SHELL exit } copyKeysSSH() { echoStageName "Copying SSH keys" if [ ! -d "$HOME/.ssh" ]; then mkdir -p "$HOME/.ssh" cp "$HOME/.dotfiles/secrets/ssh/launchpad" "$HOME/.ssh/launchpad" cp "$HOME/.dotfiles/secrets/ssh/launchpad.pub" "$HOME/.ssh/launchpad.pub" chmod 600 "$HOME/.ssh/launchpad" else [ ! -f "$HOME/.ssh/launchpad" ] && cp "$HOME/.dotfiles/secrets/ssh/launchpad" "$HOME/.ssh/launchpad" [ ! -f "$HOME/.ssh/launchpad.pub" ] && "cp $HOME/.dotfiles/secrets/ssh/launchpad.pub" "$HOME/.ssh/launchpad.pub" [ -f "$HOME/.ssh/launchpad.pub" ] && chmod 600 "$HOME/.ssh/launchpad" fi echoStageName "Linking config files" if echo $OSTYPE | grep -qE "linux-android.*"; then [ ! -f "$HOME/.ssh/config" ] && ln -s $HOME/.dotfiles/ssh-client/termux ~/.ssh/config # TODO: Write checks if it's Ubuntu or Debian # See https://superuser.com/a/741610/1124908 for details elif echo $OSTYPE | grep -qE '^linux-gnu.*' && [ -f '/etc/debian_version' ]; then [ ! -f "$HOME/.ssh/config" ] && ln -s "$HOME/.dotfiles/ssh-client/ubuntu" "$HOME/.ssh/config" fi sleep 5 } copyBashrc() { if [[ $DOTFILES_OS_NAME == "android-termux" ]]; then ln -s $HOME/.dotfiles/termux.bashrc ~/.bashrc elif [[ $DOTFILES_OS_NAME == "debian-ubuntu" ]]; then if [[ $SKIP_CONFIG_LINKING == "" ]] && [ ! -f "$HOME/.bashrc" ]; then ln -s "$HOME/.dotfiles/ubuntu.bashrc" ~/.bashrc elif [[ $GOOGLE_CLOUD_SHELL == "true" ]] && [[ $SKIP_CONFIG_LINKING == "" ]] && [ -f "$HOME/.bashrc" ]; then rm "$HOME/.bashrc" ln -s "$HOME/.dotfiles/bashrc/googlecloudshell.bashrc" "$HOME/.bashrc" elif [[ $SKIP_CONFIG_LINKING == "" ]] && [ -f "$HOME/.bashrc" ]; then if [[ -L "$HOME/.bashrc" ]]; then warn "~/.bashrc is symlinked, skipping the linking process" else warn "Existing bashrc found, renaming to ~/.bashrc.bak" mv "$HOME/.bashrc" "$HOME/.bashrc.bak" ln -s "$HOME/.dotfiles/ubuntu.bashrc" "$HOME/.bashrc" fi fi fi } copyGitConfig() { echoStageName "Symlinking Git config" if [[ $DOTFILES_OS_NAME == "android-termux" ]] && [[ $SKIP_CONFIG_LINKING == "" ]]; then [ ! -L "$HOME/.gitconfig" ] && ln -s "$HOME/.dotfiles/gitconfig/termux" ~/.gitconfig && success "Git config symlinked" || warn "Git configuration on userspace found, either symlink is broken or customizations had been made. Please fix any conflicts or soft link them manually!" # TODO: Write checks if it's Ubuntu or Debian # See https://superuser.com/a/741610/1124908 for details # By default, we'll use the one-size-fits-all Linux config for Git elif [[ $DOTFILES_OS_NAME == "debian-ubuntu" ]] && [[ $SKIP_CONFIG_LINKING == "" ]]; then [ ! -L "$HOME/.gitconfig" ] && ln -s "$HOME/.dotfiles/gitconfig/linux" "$HOME/.gitconfig" || warn "Git configuration on userspace found, either symlink is broken or customizations had been made. Please fix any conflicts or soft link them manually!" fi } copyNanoConfig() { echoStagename "Symlinking GNU nano config file" if [[ $DOTFILES_OS_NAME == "android-termux" ]]; then [ "$SKIP_CONFIG_LINKING" == "" ] && ln -s "$HOME/.dotfiles/nanorc/config/termux" "$HOME/.nanorc" else [ "$SKIP_CONFIG_LINKING" == "" ] && [ ! -L "$HOME/.gitconfig" ] && ln -s "$HOME/.dotfiles/nanorc/config/linux.nanorc" "$HOME/.nanorc" fi } installShellCheck() { echoStageName "Installing Shellcheck" scversion="stable" # or "v0.4.7", or "latest" SHELLCHECK_ARCHIVE_URL="https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" # TODO: Also detect other arches, especially on i386 if [[ $LSKIP_DEPENDENCY_INSTAL == "" ]]; then current_shellcheck_path=$(command -v shellcheck) isOwnedByUser="$(find $PREFIX/bin -user $USER -name shellcheck)" current_path_dir="$(dirname $current_shellcheck_path)" wget -qO- "$SHELLCHECK_ARCHIVE_URL" | tar -xJv -C "$HOME" if [[ $current_shellcheck_path == "" ]]; then cp "$HOME/shellcheck-${scversion}/shellcheck" "$PREFIX/bin" elif [[ $current_shellcheck_path == "$PREFIX/bin/shellcheck" ]]; then warn "Current ShellCheck install found in $PREFIX/bin, replacing with latest stable release..." if [[ $isOwnedByUser == "" ]]; then [ -f "$PREFIX/bin/shellcheck" ] && warn "Owned by either other user/root, summoning root" && sudo rm "${PREFIX}/bin/shellcheck" else rm "${PREFIX}/bin/shellcheck" fi cp "$HOME/shellcheck-${scversion}/shellcheck" "$PREFIX/bin" else warn "Current ShellCheck install found in $current_path_dir, will be removed..." if [[ $isOwnedByUser == "" ]]; then warn "Owned by either other user/root, summoning root" sudo rm "$current_path_dir/shellcheck" else rm "${PREFIX}/bin/shellcheck" fi cp "$HOME/shellcheck-${scversion}/shellcheck" "$PREFIX/bin" fi else warn "Shellcheck install/upgrade is being skipped!" fi } installAscinema() { echoStageName "Installing Asciinema" if [[ $DOTFILES_OS_NAME == "android-termux" ]] && [[ $SKIP_DEPENDENCY_INSTALL == "" ]]; then pkg install aciinema -y elif [[ $SKIP_DEPENDENCY_INSTALL == "" ]]; then if command -v python3>>/dev/null && [ -f "$HOME/.pyenv/shims/python3" ]; then "$HOME/.pyenv/shims/pip3" install asciinema --user --upgrade else pip3 install asciinema --user --upgrade fi fi } installTF() { echoStageName "Installing pip3:thefuck" if [[ $DOTFILES_OS_NAME == "android-termux" ]]; then pkg install clang -y && pip install thefuck --user --upgrade else if command -v python3>>/dev/null && [ -f "$HOME/.pyenv/shims/python3" ]; then "$HOME/.pyenv/shims/pip3" install thefuck --upgrade else pip3 install thefuck --user --upgrade fi fi } installFilterRepo() { echoStageName "Installing git-filter-repo" if [[ $DOTFILES_OS_NAME == "android-termux" ]]; then pip install git-filter-repo --upgrade else if command -v python3>>/dev/null && [ -f "$HOME/.pyenv/shims/python3" ]; then "$HOME/.pyenv/shims/pip3" install git-filter-repo --upgrade else pip3 install git-filter-repo --user --upgrade fi fi } # usage stuff usage() { echo "Accepted bootstrap script arguments are:" echo "* --help|-h - Show this text." echo "* -i - Skip installing different dependencies and packages" echo "* -d - Enable debugging" echo "* -l - Skip symlinking config files (nanorc, bashrc, etc.)" } customizeCloudShell() { echoStageName "Adding customizations for Cloud Shell Environment" if [ -f ~/.customize_environment ]; then warn "Envirnment customization script found, deleting old one..." rm -fv ~/.customize_environment fi cp ~/.dotfiles/.config/devshell.env ~/.customize_environment && chmod +x ~/.customize_environment if [ ! -d "$HOME/.cloudshell" ]; then mkdir "$HOME/.cloudshell" fi if [ ! -f "$HOME/.cloudshell/no-apt-get-warning" ]; then touch "$HOME/.cloudshell/no-apt-get-warning" fi echoStageName "Running ~/.customize_environment for you" sudo "$HOME/.customize_environment" } # Install VS Code here (or code-server) installCode() { if [[ $USE_CODE_SERVER != "" ]]; then # We'll use the officil script here, because why not? This may take longer on Termux if that's the case. echoStageName "Installing Code Server" curl -fsSL https://code-server.dev/install.sh | sh elif [[ $XDG_CURRENT_DESKTOP != "" ]]; then true # It's true for now fi } main() { # check if the help flag is here [[ $DEBUG != "" ]] && echo "flags: $*" while [[ $# -gt 0 ]]; do case $1 in "--help"|-h) shift usage exit ;; *) shift error "Invalid arguments, add the help flag for usage." && exit 1 esac shift done # import colors and show the banner useColor banner # step 1: check the OS first checkOs # step 2: install needed tools and create ~/.local/bin installDeps userspcaeBinDirCheck [[ $USE_GH_SECRETS_MIRROR != "" ]] && ghCli # Possibly interactively sign in to the CLI if GITHUB_TOKEN isn't provided [[ $GITHUB_TOKEN == "" && $USE_GH_SECRETS_MIRROR == "" ]] && gh auth login # step 3.1: then clone the repo cloneRepo # step 3.2: if we're in Cloud Shell, do this [[ $GOOGLE_CLOUD_SHELL == "true" ]] && customizeCloudShell # step 4: install additional needed tools installAscinema installTF installFilterRepo installShellCheck [[ $USE_NVM != "" ]] && installNodeVerManager installCode # step 5: copy and symlink files copyGitConfig copyNanoConfig copyBashrc # step 5.2: copy our secrets btw cloneSecretsRepo # Run this before we even copy the ssh keys! copyKeysSSH # step 6: finally clean up bullshit cleanup } main "$@" } # curybrackets is included to ensure everything is downloaded