Bootstrapping AWS EC2 Devbox
I want to write a shell script that sets up an AWS EC2 instance running Amazon Linux
2023 for remote development works. For now I will work with x86_64, but later I might
want to extend into aarch64. For developing this script, I will use a c7i.large
instance, which as 2 virtual cores and 4 GiB of RAM.
After the instance launches, grab the public IPv4 (or the public DNS record), and add to the local SSH config:
Host aws-ec2-devbox
HostName <public IPv4 or public DNS record>
IdentityFile /path/to/private/key.pem
User <usually ec2-user>
RequestTTY force
At the time of writing this script, Amazon Linux 2023 ships with curl and wget, so bootstrapping can be as straightforward as downloading the script and executing it.
curl https://xuganyu96.github.io/assets/aws-bootstrap.sh | sh -s
First, I want this script to be idempotent. This will be implemented by leaving a
short log file at $HOME/.bootstrap/log.txt. If this file exists, then print this
file, else run the bootstrapping steps.
BOOTSTRAP_LOG_PATH="${HOME}/.bootstrap.log"
if [ -f ${BOOTSTRAP_LOG_PATH} ]; then
cat ${BOOTSTRAP_LOG_PATH}
exit 1
fi
echo "Bootstrapping last finished: $(date)" >> ${BOOTSTRAP_LOG_PATH}
The bottom layer of bootstrapping is the system packages. With Amazon Linux, dnf
is the default package manager. Notably, Docker is installed with dnf, unlike with
Fedora Workstation edition, where I need to manage external rpm repository.
sudo dnf update -y \
&& sudo dnf install -y git docker gcc clang cmake nodejs npm lldb \
&& sudo dnf groupinstall -y "Development Tools" \
&& sudo dnf install -y zlib-devel bzip2 bzip2-devel readline-devel sqlite \
sqlite-devel tk-devel libffi-devel xz-devel openssl-devel ninja-build \
valgrind-devel \
&& sudo systemctl enable docker \
&& sudo systemctl start docker \
&& sudo usermod -aG docker "$USER" \
&& sudo docker run hello-world \
&& sudo chsh -s $(which zsh) $USER
Amazon Linux ships bash by default. I want to switch to zsh and install Oh My Zsh:
sudo dnf install -y zsh util-linux-user
sudo chsh -s $(which zsh) $USER
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
Next, I use tmux with my custom config. My config will display a red status bar indicating tmux is running on remote. It is generally recommended to not nest tmux inside tmux.
sudo dnf install -y tmux \
&& wget -O ~/.tmux.conf \
https://raw.githubusercontent.com/xuganyu96/xuganyu96.github.io/refs/heads/main/tmux.conf
I use a custom global gitignore:
wget -O ~/.gitignore https://raw.githubusercontent.com/xuganyu96/xuganyu96.github.io/refs/heads/main/global.gitignore \
&& git config --global core.excludesFile "~/.gitignore"
Last of the essential tools is Neovim:
curl -fLO https://github.com/neovim/neovim/releases/download/v0.11.7/nvim-linux-x86_64.tar.gz \
&& sudo rm -rf /opt/nvim-linux-x86_64 \
&& sudo tar -C /opt -xzvf nvim-linux-x86_64.tar.gz \
&& echo 'export PATH="/opt/nvim-linux-x86_64/bin:$PATH"' >> ~/.zshrc \
&& mkdir -p ~/.config/nvim
&& wget -O ~/.config/nvim/init.lua \
https://raw.githubusercontent.com/xuganyu96/xuganyu96.github.io/refs/heads/main/neovim/init.lua
The system packages already installed my C programming tool chain. I also program Python and Rust. For Python, I will install pyenv but stop before installing specific interpreter. For Rust, the installer script can just go brrr.
git clone https://github.com/pyenv/pyenv.git ~/.pyenv \
&& cd ~/.pyenv && src/configure && make -C src \
&& echo '# [pyenv]' >> ~/.zshrc \
&& echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc \
&& echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc \
&& echo 'eval "$(pyenv init - zsh)"' >> ~/.zshrc \
curl https://sh.rustup.rs -sSf | sh -s -- -y \
&& source ~/.cargo/env \
&& cargo install ripgrep
The complete script can be found at assets/aws-bootstrap.sh.