Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

## Project Overview

Ubuntu Server Bootstrap is an opinionated bash script for provisioning Ubuntu 22+ servers with development and DevOps tools. It installs Docker CE, Docker Compose v2, Zsh with Prezto, and a curated set of CLI tools (ripgrep, fd, fzf, ag, tig, htop, byobu, neovim, etc.).
Ubuntu Server Bootstrap is an opinionated bash script for provisioning Ubuntu 22+ servers with development and DevOps tools. It installs Docker CE, Docker Compose v2, Zsh with Prezto, and a curated set of CLI tools (ripgrep, fd, fzf, ag, tig, htop, byobu, neovim, lazygit, etc.). Supports CLI flags (e.g. `--nvim-deb`, `--help`).

**Supported Ubuntu LTS versions:** 22.04, 24.04 (x86_64 only)

## Project Structure

```
bootstrap.sh # Main provisioning script (~393 lines)
bootstrap.sh # Main provisioning script (~660 lines)
run-tests.sh # Test runner - builds Docker images per Ubuntu version
Dockerfile # Container definition for testing
.github/workflows/ci.yaml # GitHub Actions CI (matrix: 22.04, 24.04)
Expand All @@ -25,8 +25,9 @@ Dockerfile # Container definition for testing
- Error/debug/SIGINT trap handlers are defined for logging context
- Variables use `SCREAMING_SNAKE_CASE` and `${VAR}` interpolation (not `$VAR`)
- Functions are documented with usage, arguments, return values, and examples
- Utility functions: `log()`, `logError()`, `runCmdAndLog()`, `currentDate()`
- Utility functions: `log()`, `logError()`, `runCmdAndLog()`, `currentDate()`, `usage()`
- GitHub API helpers: `getLatestReleaseForRepo()`, `downloadBinaryLatestRelease()`
- CLI argument parsing via `while`/`case` loop (`--nvim-deb`, `--help`)

### Installation Pattern

Expand Down
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
# Ubuntu Server Bootstrap

A slightly opinionated and straightforward script to setup base Ubuntu 18+ servers environment with:
A slightly opinionated and straightforward script to setup base Ubuntu 22+ servers environment with:

- Docker CE
- [Docker Compose v2](https://github.com/docker/compose)
- [Docker Compose Switch](https://github.com/docker/compose-switch)
- ZSH with [Prezto](https://github.com/sorin-ionescu/prezto)
- Symlinks `python3` to `python` if `python` command is not found
- Tools:
- `build-essential`: Essential build tools (gcc, make, etc.)
- [`byobu`](https://ubuntu.com/server/docs/tools-byobu): Enhancement to multiplexers like `screen` or `tmux`
- `curl`
- [`fd-find`](https://github.com/sharkdp/fd): A simple, fast and user-friendly alternative to 'find'
- [`fzf`](https://github.com/junegunn/fzf): A command-line fuzzy finder
- `git`
- `htop`: Better `top`
- `neovim`
- [`lazygit`](https://github.com/jesseduffield/lazygit): A simple terminal UI for git commands
- [`neovim`](https://neovim.io/): Hyperextensible Vim-based text editor (set as default `vi`/`vim`)
- [`ripgrep`](https://github.com/BurntSushi/ripgrep): Recursively searches directories for a regex pattern while respecting your gitignore
- [`silversearcher-ag`](https://github.com/ggreer/the_silver_searcher): A code-searching tool similar to ack, but faster
- [SpeedTest CLI](https://github.com/sivel/speedtest-cli)
- [`tig`](https://jonas.github.io/tig/): CLI Git client
- `unzip`
- `vim`
- `wget`
- `zip`
- [SpaceVim](https://spacevim.org/)
- [SpeedTest CLI](https://github.com/sivel/speedtest-cli)

> **This script is intended to be run as `root`**.
>
> When deploying a VPS in many providers (like Digital Ocean, Vultr, OVH, Contabo, etc), you will get the instance with only the `root` by default. Make sure to create a user for your daily use.

It's tested with the following Ubuntu LTS versions:

- `20.04`
- `22.04`
- `24.04`

Expand All @@ -51,6 +51,26 @@ Or with `curl` if already installed:
curl -s https://raw.githubusercontent.com/yorch/ubuntu-server-bootstrap/main/bootstrap.sh | bash
```

### Options

To see all available options:

```bash
bash bootstrap.sh --help
```

| Flag | Description |
|------|-------------|
| `--nvim-deb` | Install NeoVim from GitHub releases `.deb` package (default: PPA unstable) |
| `--help` | Show usage information and exit |

To use flags, download the script first:

```bash
wget -q -O bootstrap.sh https://raw.githubusercontent.com/yorch/ubuntu-server-bootstrap/main/bootstrap.sh
bash bootstrap.sh --nvim-deb
```

This will take a few minutes, after its done, you might want to restart the box in case there is a newer kernel installed that just got installed.

At the minimum, you should log out and log in again so `zsh` gets activated on your session.
Expand Down
91 changes: 87 additions & 4 deletions bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,35 @@ USR_BIN_DIR=/usr/local/bin
# -f, --fail Fail silently (no output at all) on HTTP errors
CURL_CMD="curl -sSLf"

NVIM_USE_DEB=false
SPINNER_PID=""

UBUNTU_SUPPORTED_VERSIONS=(
"22.04"
"24.04"
)

###############################################################################
# Usage
###############################################################################

# Print usage information and available options
# Usage: usage
# Arguments: none
# Returns: 0
# Example: usage
function usage() {
echo "Usage: $(basename "${0}") [OPTIONS]"
echo
echo "Bootstrap an Ubuntu server with development and DevOps tools."
echo
echo "Options:"
echo " --nvim-deb Install NeoVim from GitHub releases .deb package"
echo " (default: install from PPA unstable)"
echo " --help Show this help message and exit"
echo
}

###############################################################################
# Utils
###############################################################################
Expand Down Expand Up @@ -293,6 +315,7 @@ function stepSetLocales() {
function stepInstallTools() {
logStep "Installing tools..."
runCmdAndLog ${APT_INSTALL} \
build-essential \
byobu \
curl \
fd-find \
Expand Down Expand Up @@ -384,6 +407,35 @@ function stepInstallDockerSwitch() {
fi
}

# Install LazyGit TUI from GitHub releases
# Usage: stepInstallLazyGit
# Arguments: none
# Returns: 0 on success, non-zero on error
# Example: stepInstallLazyGit
function stepInstallLazyGit() {
local LAZYGIT_BIN="${USR_BIN_DIR}/lazygit"
local LAZYGIT_REPO="jesseduffield/lazygit"
local LAZYGIT_TMP_FILE="/tmp/lazygit.tar.gz"
if ! [ -e "${LAZYGIT_BIN}" ]; then
logStep "Installing LazyGit..."
local LAZYGIT_VERSION
LAZYGIT_VERSION=$(getLatestReleaseForRepo "${LAZYGIT_REPO}")
# Remove v prefix from version string
LAZYGIT_VERSION="${LAZYGIT_VERSION#v}"
local LAZYGIT_ASSET="lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
downloadLatestReleaseArtifact \
"${LAZYGIT_REPO}" \
"${LAZYGIT_ASSET}" \
"${LAZYGIT_TMP_FILE}"
Comment on lines +421 to +429

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In stepInstallLazyGit, you call getLatestReleaseForRepo directly to build LAZYGIT_ASSET and then call downloadLatestReleaseArtifact, which internally calls getLatestReleaseForRepo again to compute the release tag. If a new LazyGit release is published between these two calls, the asset name (derived from the first version value) may no longer exist under the tag returned by the second call, causing a 404/download failure. To avoid this potential version mismatch and the redundant API call, refactor so that the version is resolved exactly once (either by passing a version into the download helper or by having the helper return both tag and asset name), and ensure the asset name and release tag are always computed from the same version value.

Copilot uses AI. Check for mistakes.
runCmdAndLog tar xf "${LAZYGIT_TMP_FILE}" -C /tmp lazygit
runCmdAndLog install /tmp/lazygit -D -t "${USR_BIN_DIR}/"
runCmdAndLog rm -rf "${LAZYGIT_TMP_FILE}" /tmp/lazygit
Comment on lines +417 to +432

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stepInstallLazyGit downloads the latest lazygit release artifact from the third‑party GitHub repo jesseduffield/lazygit via downloadLatestReleaseArtifact and installs the extracted binary system‑wide as root, without pinning to a specific version or verifying the artifact’s integrity. If the upstream GitHub repository, the releases/latest pointer, or the download path is compromised, an attacker can ship a malicious lazygit binary that will be transparently installed and later executed with user privileges, effectively enabling remote code execution on machines using this bootstrap script. To reduce this supply‑chain risk, pin to a specific trusted release (or commit) and verify its checksum or signature before installation instead of always installing the moving "latest" artifact.

Copilot uses AI. Check for mistakes.
log "LazyGit installed."
else
logStep "LazyGit already installed."
fi
}

# Install NeoVim and set it as the default vi/vim editor
# Usage: stepInstallNeoVim
# Arguments: none
Expand All @@ -392,10 +444,21 @@ function stepInstallDockerSwitch() {
function stepInstallNeoVim() {
if ! [ -e "$(command -v nvim)" ]; then
logStep "Installing NeoVim..."
# Adds repo for latest neovim version
runCmdAndLog add-apt-repository -y ppa:neovim-ppa/unstable
runCmdAndLog ${APT_CMD} update
runCmdAndLog ${APT_INSTALL} neovim
if [ "${NVIM_USE_DEB}" = true ]; then
# Install from GitHub releases .deb package
local NVIM_TMP_FILE="/tmp/nvim.deb"
downloadLatestReleaseArtifact \
"neovim/neovim-releases" \
"nvim-linux-x86_64.deb" \
"${NVIM_TMP_FILE}"
runCmdAndLog ${APT_INSTALL} "${NVIM_TMP_FILE}"
runCmdAndLog rm -f "${NVIM_TMP_FILE}"
else
# Install from PPA (default)
runCmdAndLog add-apt-repository -y ppa:neovim-ppa/unstable
runCmdAndLog ${APT_CMD} update
runCmdAndLog ${APT_INSTALL} neovim
fi
# Set neovim as default vim
local NVIM_BIN
NVIM_BIN="$(command -v nvim)"
Expand Down Expand Up @@ -537,6 +600,25 @@ if [ "$(uname -m)" != "x86_64" ]; then
exit 1
fi

# Parse command-line arguments
while [ $# -gt 0 ]; do
case "${1}" in
--nvim-deb)
NVIM_USE_DEB=true
;;
--help)
usage
exit 0
;;
*)
logError "Unknown option: ${1}"
usage
exit 1
;;
esac
shift
done

STEPS=(
stepUpgradePackages
stepSetTimezone
Expand All @@ -545,6 +627,7 @@ STEPS=(
stepInstallDocker
stepInstallDockerCompose
stepInstallDockerSwitch
stepInstallLazyGit
stepInstallNeoVim
stepInstallSpeedTest
stepSetupPython
Expand Down