diff --git a/README.md b/README.md index dc8689c..ec26c31 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,45 @@ ## Bootstrap Script Documentation ### Scope -This document details the usage of a server bootstrap script aimed at installing essential utilities and configurations for server setups. It supports the installation of ZSH, Docker, along with various optimizations and support tools. +A server bootstrap script that installs essential utilities and configurations: ZSH (with Oh My Zsh and plugins), Docker (with the Compose v2 plugin), system tooling (htop, glances, iftop, ctop), and optional provider / Salt configuration. Targets Debian/Ubuntu hosts (`apt-get`) on `amd64` or `arm64`. Re-runs are safe — edits to `.zshrc` and SSH keys are idempotent. ### Usage -To initiate the bootstrap process with the default configuration, embed the following command in your server's post-installation scripts: +To run the bootstrap with the default configuration on a fresh host (as root): ```bash -#!/usr/bin/env bash -## source <(curl -s https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/strap.sh) defaults-bootstrap +source <(curl -fsSL https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/strap.sh) defaults-bootstrap ``` -This line fetches and executes the bootstrap script, setting up the server with a standard suite of tools and settings. +This fetches and executes the bootstrap script, applying a standard suite of tools and settings. -### Advanced Usage and Provider Specific Deploys +### Advanced Usage and Provider-specific Deploys -The bootstrap script accommodates deployments specific to different providers, allowing for the inclusion or exclusion of selected features based on requirements. +The bootstrap supports per-provider configuration and selective feature toggles via positional args. -#### Example for OVH Deployment: -For deploying on an OVH server with tailored settings, use the example below: +#### Example for OVH deployment ```bash -#!/usr/bin/env bash -source <(curl -s https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/strap.sh) bootstrap ovh nosalt +source <(curl -fsSL https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/strap.sh) bootstrap ovh nosalt ``` -This example demonstrates how to configure a server specifically for OVH, omitting Salt configuration management. +#### Argument reference +- `strap.sh defaults-bootstrap` — provider=`none`, salt=`nosalt`. Also the default when no args are given. +- `strap.sh bootstrap PROVIDER SALT` where: + - `PROVIDER` ∈ {`none`, `ovh`, `digitalocean`} + - `SALT` ∈ {`salt`, `nosalt`} -#### Deployment Customization: -- The first argument after `bootstrap` indicates the target cloud provider or environment (e.g., `ovh`, `digitalocean`). -- The second argument allows for specifying configuration preferences, such as excluding Salt stack installation (`nosalt`). +### Environment overrides -Adjust these parameters to customize the bootstrap process according to your deployment environment and preferences. \ No newline at end of file +| Variable | Default | Purpose | +| --- | --- | --- | +| `STRAP_BASE_URL` | `https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main` | Base URL for fetching `bootstrap.sh` / `zsh-setup.sh` | +| `STRAP_AUTHORIZED_KEYS` | built-in `defaultkey_key` | SSH public key appended to `/root/.ssh/authorized_keys` | +| `STRAP_SENTRY_DSN` | `https://...@sentry.aenow.com/3` | Sentry DSN exported in `.zshrc` | +| `STRAP_SALT_MASTER` | `aerence.aenow.fun` | Salt master address (only used when `SALT=salt`) | + +Example: + +```bash +STRAP_AUTHORIZED_KEYS="$(cat ~/.ssh/id_ed25519.pub)" \ + source <(curl -fsSL https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/strap.sh) defaults-bootstrap +``` diff --git a/docker-compose.yml b/docker-compose.yml index 540e4b4..d8e057e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.7' services: bootstrap-scripts: build: diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 5f22488..9694f3a 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -1,74 +1,96 @@ -#!/bin/bash -set -e +#!/usr/bin/env bash +set -euo pipefail -# Sentry setup -echo 'export SENTRY_DSN=https://4d089076433c4a7aa31bbb2741f053fe@sentry.aenow.com/3' >> ~/.zshrc -eval "$(sentry-cli bash-hook)" +: "${STRAP_SENTRY_DSN:=https://4d089076433c4a7aa31bbb2741f053fe@sentry.aenow.com/3}" +: "${STRAP_SALT_MASTER:=aerence.aenow.fun}" + +PROVIDER="${1:-none}" +SALT_MODE="${2:-nosalt}" + +# shellcheck disable=SC1091 +. /etc/os-release +CODENAME="${UBUNTU_CODENAME:-${VERSION_CODENAME:-jammy}}" +VERSION_ID_SHORT="${VERSION_ID:-22.04}" +ARCH="$(dpkg --print-architecture)" +HOME_DIR="${HOME:-/root}" +ZSHRC="$HOME_DIR/.zshrc" + +append_unique() { + local line="$1" file="$2" + touch "$file" + grep -qxF "$line" "$file" || echo "$line" >> "$file" +} + +append_unique "export SENTRY_DSN=$STRAP_SENTRY_DSN" "$ZSHRC" -# Functions for provider-specific configurations digitalocean() { - export HOSTNAME=$(curl -s http://169.254.169.254/metadata/v1/hostname) - hostnamectl set-hostname "$HOSTNAME" - export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address) - export PUBLIC_IPV6=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv6/address) + local md_base=http://169.254.169.254/metadata/v1 + local hostname + hostname=$(curl -fsS "$md_base/hostname" || true) + if [ -n "$hostname" ]; then + hostnamectl set-hostname "$hostname" + fi } ovh() { - echo "Nothing special for OVH at this stage." + echo "No OVH-specific configuration needed." } -# Provider setup -case $1 in +case "$PROVIDER" in digitalocean) digitalocean ;; - ovh) ovh ;; - none) echo "Nothing special going to be done here." ;; - *) echo "bootstrap options are:" - echo "bootstrap ovh [salt/nosalt]" - echo "bootstrap digitalocean [salt/nosalt]" - echo "bootstrap none [salt/nosalt]" ;; + ovh) ovh ;; + none) echo "No provider-specific configuration." ;; + *) + echo "Unknown provider '$PROVIDER'. Options: none, ovh, digitalocean" >&2 + exit 1 + ;; esac -# Salt installation install_salt() { - sudo curl -fsSL -o /usr/share/keyrings/salt-archive-keyring.gpg https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/latest/salt-archive-keyring.gpg - echo "deb [signed-by=/usr/share/keyrings/salt-archive-keyring.gpg arch=amd64] https://repo.saltproject.io/salt/py3/ubuntu/22.04/amd64/latest jammy main" | sudo tee /etc/apt/sources.list.d/salt.list + curl -fsSL -o /usr/share/keyrings/salt-archive-keyring.gpg \ + "https://repo.saltproject.io/salt/py3/ubuntu/${VERSION_ID_SHORT}/${ARCH}/latest/salt-archive-keyring.gpg" + echo "deb [signed-by=/usr/share/keyrings/salt-archive-keyring.gpg arch=${ARCH}] https://repo.saltproject.io/salt/py3/ubuntu/${VERSION_ID_SHORT}/${ARCH}/latest ${CODENAME} main" \ + > /etc/apt/sources.list.d/salt.list mkdir -p /etc/salt/minion.d/ - echo 'master: aerence.aenow.fun' > /etc/salt/minion.d/99-master-address.conf - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install -y salt-minion + echo "master: $STRAP_SALT_MASTER" > /etc/salt/minion.d/99-master-address.conf + DEBIAN_FRONTEND=noninteractive apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends salt-minion } -# Salt installation based on user selection -case $2 in - salt) install_salt ;; - nosalt) echo "Not installing salt." ;; - *) echo "No salt instructions received." - echo "Options are:" - echo "bootstrap [hostingProvider] salt" - echo "bootstrap [hostingProvider] nosalt" ;; +case "$SALT_MODE" in + salt) install_salt ;; + nosalt) echo "Skipping salt-minion install." ;; + *) + echo "Unknown salt mode '$SALT_MODE'. Options: salt, nosalt" >&2 + exit 1 + ;; esac -# Apt package installations -echo "Installing Apt Packages" -apt-get update -DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::='--force-confold' --force-yes -fuy dist-upgrade -DEBIAN_FRONTEND=noninteractive apt-get install -y asciinema ca-certificates gnupg git glances htop iftop zsh -apt-get update +echo "Installing apt packages" +DEBIAN_FRONTEND=noninteractive apt-get update +DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::='--force-confold' -y dist-upgrade +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + asciinema ca-certificates gnupg git glances htop iftop zsh -# Docker and docker-compose installation -echo "Install docker-compose and docker via convenience scripts" -curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose -chmod +x /usr/local/bin/docker-compose -curl -fsSL https://get.docker.com -o get-docker.sh -sh get-docker.sh -DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin +echo "Installing Docker and compose plugin" +if ! command -v docker >/dev/null 2>&1; then + curl -fsSL https://get.docker.com -o /tmp/get-docker.sh + sh /tmp/get-docker.sh + rm -f /tmp/get-docker.sh +fi +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends docker-compose-plugin -# CTOP installation echo "Installing CTOP" -wget https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -O /usr/local/bin/ctop -chmod +x /usr/local/bin/ctop +case "$ARCH" in + amd64|arm64) + curl -fsSL "https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-${ARCH}" \ + -o /usr/local/bin/ctop + chmod +x /usr/local/bin/ctop + ;; + *) + echo "Skipping CTOP (unsupported arch: $ARCH)" >&2 + ;; +esac -# Setup Oh My Zsh on first login -curl -o /root/zsh-setup.sh https://bootstrap:sHEG3NTC6og8pCJDTF6EPYb8jLmbskx5Ns@git.nixc.us/colin/bootstrap-scripts/raw/branch/main/scripts/zsh-setup.sh -echo "zsh-setup" >> ~/.profile -source ~/.profile +echo "Running zsh setup" +STRAP_SENTRY_DSN="$STRAP_SENTRY_DSN" /usr/local/sbin/zsh-setup "$PROVIDER" ae-sentry diff --git a/scripts/zsh-setup.sh b/scripts/zsh-setup.sh index 9de8f02..3b8bc79 100644 --- a/scripts/zsh-setup.sh +++ b/scripts/zsh-setup.sh @@ -1,68 +1,108 @@ #!/usr/bin/env bash +set -euo pipefail -USRDIR=$(echo ~) +: "${STRAP_SENTRY_DSN:=https://4d089076433c4a7aa31bbb2741f053fe@sentry.aenow.com/3}" -# Simplify installation command -curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh | sh -s -- --unattended +USR_HOME="${HOME:-/root}" +ZSHRC="$USR_HOME/.zshrc" -# Update ZSH theme directly -sed -i'' -e 's@ZSH_THEME="robbyrussell"@ZSH_THEME="pygmalion"@' "$USRDIR/.zshrc" +append_unique() { + local line="$1" file="$2" + touch "$file" + grep -qxF "$line" "$file" || echo "$line" >> "$file" +} + +clone_plugin() { + local repo="$1" dest="$2" + [ -d "$dest" ] || git clone --depth=1 "$repo" "$dest" +} + +usage() { + echo "Usage: zsh-setup [digitalocean|ovh|none] [ae-sentry|none]" >&2 +} + +install_omz() { + if [ ! -d "$USR_HOME/.oh-my-zsh" ]; then + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended + fi + if [ -f "$ZSHRC" ]; then + sed -i -e 's@ZSH_THEME="robbyrussell"@ZSH_THEME="pygmalion"@' "$ZSHRC" + fi +} ae_sentry() { - echo 'export SENTRY_DSN=https://4d089076433c4a7aa31bbb2741f053fe@sentry.aenow.com/3' >> "$USRDIR/.zshrc" + append_unique "export SENTRY_DSN=$STRAP_SENTRY_DSN" "$ZSHRC" } digitalocean() { - echo 'export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)' >> "$USRDIR/.zshrc" - echo 'export PUBLIC_IPV6=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv6/address)' >> "$USRDIR/.zshrc" + # shellcheck disable=SC2016 + append_unique 'export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)' "$ZSHRC" + # shellcheck disable=SC2016 + append_unique 'export PUBLIC_IPV6=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv6/address)' "$ZSHRC" } ovh() { - echo "Nothing to see here at the moment." + echo "No OVH-specific zsh configuration." } configure_plugins() { - # Clone only if directory does not exist to prevent errors on rerun - [ ! -d "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" ] && git clone https://github.com/zsh-users/zsh-autosuggestions "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" - [ ! -d "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" ] && git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" - [ ! -d "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/zsh-history-substring-search" ] && git clone https://github.com/zsh-users/zsh-history-substring-search "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/zsh-history-substring-search" - [ ! -d "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/command-time" ] && git clone https://github.com/popstas/zsh-command-time.git "${ZSH_CUSTOM:-$USRDIR/.oh-my-zsh/custom}/plugins/command-time" - - cat <> "$USRDIR/.zshrc" -ZSH_COMMAND_TIME_MIN_SECONDS=3 -ZSH_COMMAND_TIME_MSG="Execution time: %s" -ZSH_COMMAND_TIME_COLOR="cyan" -ZSH_COMMAND_TIME_EXCLUDE=(vim mcedit nano ctop ssh) -EOF + local custom="${ZSH_CUSTOM:-$USR_HOME/.oh-my-zsh/custom}" + clone_plugin https://github.com/zsh-users/zsh-autosuggestions "$custom/plugins/zsh-autosuggestions" + clone_plugin https://github.com/zsh-users/zsh-syntax-highlighting.git "$custom/plugins/zsh-syntax-highlighting" + clone_plugin https://github.com/zsh-users/zsh-history-substring-search "$custom/plugins/zsh-history-substring-search" + clone_plugin https://github.com/popstas/zsh-command-time.git "$custom/plugins/command-time" - sed -i'' -e 's@plugins=(git)@plugins=(git cp colored-man-pages docker docker-compose extract iterm2 python rsync safe-paste transfer ubuntu zsh-navigation-tools zsh-autosuggestions zsh-syntax-highlighting zsh-history-substring-search command-time universalarchive)@' "$USRDIR/.zshrc" + append_unique 'ZSH_COMMAND_TIME_MIN_SECONDS=3' "$ZSHRC" + append_unique 'ZSH_COMMAND_TIME_MSG="Execution time: %s"' "$ZSHRC" + append_unique 'ZSH_COMMAND_TIME_COLOR="cyan"' "$ZSHRC" + append_unique 'ZSH_COMMAND_TIME_EXCLUDE=(vim mcedit nano ctop ssh)' "$ZSHRC" + + local plugin_line='plugins=(git cp colored-man-pages docker docker-compose extract iterm2 python rsync safe-paste transfer ubuntu zsh-navigation-tools zsh-autosuggestions zsh-syntax-highlighting zsh-history-substring-search command-time universalarchive)' + if grep -q '^plugins=(git)$' "$ZSHRC"; then + sed -i -e "s@^plugins=(git)\$@$plugin_line@" "$ZSHRC" + elif ! grep -qxF "$plugin_line" "$ZSHRC"; then + echo "$plugin_line" >> "$ZSHRC" + fi } -# Process options -case $1 in +clean_profile_remnants() { + if [ -f "$USR_HOME/.profile" ]; then + sed -i -e '/^zsh-setup$/d' -e '/zsh-setup\.sh/d' "$USR_HOME/.profile" + fi +} + +install_omz + +case "${1:-none}" in digitalocean) digitalocean ;; - ovh) ovh ;; - *) echo "Usage: zsh-setup.sh [digitalocean|ovh|none]" ;; + ovh) ovh ;; + none) ;; + *) usage ;; esac -case $2 in +case "${2:-none}" in ae-sentry) ae_sentry ;; - none) echo "No Sentry server added." ;; - *) echo "Usage: zsh-setup.sh hosting-provider [ae-sentry|my-sentry|none]" ;; + none) ;; + *) usage ;; esac -echo "export DOCKER_BUILDKIT=1" >> "$USRDIR/.zshrc" -echo "export COMPOSE_DOCKER_CLI_BUILD=1" >> "$USRDIR/.zshrc" +append_unique 'export DOCKER_BUILDKIT=1' "$ZSHRC" +append_unique 'export COMPOSE_DOCKER_CLI_BUILD=1' "$ZSHRC" configure_plugins +clean_profile_remnants -sed -i'' -e "/bash \$USRDIR\/zsh-setup.sh/d" "$USRDIR/.profile" +ZSH_BIN="$(command -v zsh || true)" +if [ -n "$ZSH_BIN" ]; then + USER_NAME="$(id -un)" + CURRENT_SHELL="$(getent passwd "$USER_NAME" | cut -d: -f7 || true)" + if [ "$CURRENT_SHELL" != "$ZSH_BIN" ]; then + chsh -s "$ZSH_BIN" "$USER_NAME" || true + fi +fi -# Consider not removing the script automatically for debugging or rerun -# rm -f "$USRDIR/zsh-setup.sh" +echo "Bootstrap complete. Relog to start zsh." -echo "Relog into terminal finished bootstrapping server" - -chsh -s "$(which zsh)" -zsh -source "$USRDIR/.zshrc" +if [ -n "$ZSH_BIN" ] && [ -t 0 ] && [ -t 1 ]; then + exec "$ZSH_BIN" +fi diff --git a/strap.sh b/strap.sh index 73a519b..64a5935 100644 --- a/strap.sh +++ b/strap.sh @@ -1,27 +1,59 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -# Basic dependencies installation -DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y curl wget +: "${STRAP_BASE_URL:=https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main}" +: "${STRAP_AUTHORIZED_KEYS:=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxoakgL0Tq4mAv+UMnFc3PZptPCXz8ObCsyVmBtiB2P defaultkey_key}" -# Downloading bootstrap scripts -curl -o /usr/local/sbin/zsh-setup https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/scripts/zsh-setup.sh && chmod +x /usr/local/sbin/zsh-setup -curl -o /usr/local/sbin/bootstrap https://git.nixc.us/colin/bootstrap-scripts/raw/branch/main/scripts/bootstrap.sh && chmod +x /usr/local/sbin/bootstrap +usage() { + cat < /root/.ssh/authorized_keys +Environment overrides: + STRAP_BASE_URL base URL for fetching scripts + STRAP_AUTHORIZED_KEYS ssh public key(s) to install (default: built-in defaultkey) +USAGE +} + +if [ "${EUID:-$(id -u)}" -ne 0 ]; then + echo "strap.sh must be run as root" >&2 + exit 1 +fi + +if ! command -v apt-get >/dev/null 2>&1; then + echo "strap.sh requires a Debian/Ubuntu host (apt-get not found)" >&2 + exit 1 +fi + +DEBIAN_FRONTEND=noninteractive apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends curl wget ca-certificates + +curl -fsSL -o /usr/local/sbin/zsh-setup "$STRAP_BASE_URL/scripts/zsh-setup.sh" +curl -fsSL -o /usr/local/sbin/bootstrap "$STRAP_BASE_URL/scripts/bootstrap.sh" +chmod +x /usr/local/sbin/zsh-setup /usr/local/sbin/bootstrap -# Ensure .ssh directory exists and proper permissions are set mkdir -p /root/.ssh chmod 700 /root/.ssh +touch /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys +grep -qxF "$STRAP_AUTHORIZED_KEYS" /root/.ssh/authorized_keys \ + || echo "$STRAP_AUTHORIZED_KEYS" >> /root/.ssh/authorized_keys -# Run bootstrap based on input parameters or default action -case $1 in +case "${1:-defaults-bootstrap}" in bootstrap) - /usr/local/sbin/bootstrap "$2" "$3" "$4" + /usr/local/sbin/bootstrap "${2:-none}" "${3:-nosalt}" ;; - defaults-bootstrap|*) - /usr/local/sbin/bootstrap none nogluster nosalt + defaults-bootstrap) + /usr/local/sbin/bootstrap none nosalt + ;; + -h|--help|help) + usage + ;; + *) + echo "Unknown command: ${1:-}" >&2 + usage >&2 + exit 1 ;; esac