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
5 changes: 3 additions & 2 deletions .github/workflows/shell-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- develop
pull_request:

jobs:
Expand All @@ -13,8 +14,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Install ShellCheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Install shell tools
run: sudo apt-get update && sudo apt-get install -y shellcheck zsh

- name: Set up Go
uses: actions/setup-go@v5
Expand Down
61 changes: 26 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
# Ubuntu 24.04 Developer Bootstrap (WSL2 + native)
# Ubuntu 26.04 Developer Bootstrap

Reproducible, idempotent bootstrap for Ubuntu 24.04 that installs a full developer distro and applies dotfiles via chezmoi.
Idempotent developer bootstrap for Ubuntu 26.04 LTS on bare-metal/VM installations and WSL2. The installer detects WSL automatically; Windows integration is never configured on native Ubuntu.

## One-liner install

```bash
git clone https://github.com/w0rldx/dotfiles.git ~/.dotfiles && bash ~/.dotfiles/bootstrap/install.sh
```

## WSL2 systemd requirement (mandatory for Podman rootless)
## Platform-specific behavior

Podman rootless socket activation uses `systemctl --user`, which requires systemd. If running in WSL2:
On native Ubuntu, the bootstrap uses the normal Linux PATH and optionally installs native VS Code. Under WSL it additionally:

- removes conflicting Windows Node.js, pnpm, and PowerShell entries from PATH;
- skips the native VS Code package and prints WSL extension guidance;
- prints WSL-specific systemd guidance only when systemd is unavailable.

## WSL2 systemd (optional)

Rootless Docker uses systemd user services when available. If systemd is not running, the bootstrap falls back to starting `dockerd-rootless.sh` in the user session and zsh auto-starts it on login. If running in WSL2 and you want systemd:

1. Edit `/etc/wsl.conf`:

Expand All @@ -29,6 +37,13 @@ wsl.exe --shutdown

Microsoft Learn: https://learn.microsoft.com/windows/wsl/systemd

## Docker (rootless)

- Installed from Docker's official Ubuntu apt repo (docker-ce, docker-ce-rootless-extras, etc.).
- `DOCKER_HOST` is set to `unix://$XDG_RUNTIME_DIR/docker.sock` with a fallback to `~/.docker/run` when systemd is unavailable.
- With systemd: user service `docker.service` is enabled and linger is turned on.
- Without systemd: `~/.config/docker/rootless-session.sh` starts `dockerd-rootless.sh` on login if it's not already running.

## VS Code on WSL (recommended)

- Install VS Code on Windows (not inside WSL).
Expand All @@ -44,25 +59,9 @@ Native Linux VS Code is opt-in. Set the environment variable to install via the
INSTALL_VSCODE_LINUX=1 ./bootstrap/install.sh
```

## Apt PPAs

- The bootstrap adds `ppa:zhangsongcui3371/fastfetch` before apt installs.
- Add additional PPAs in `bootstrap/steps/05-apt-ppa.sh`.
## Mise-managed developer toolchain

## Neovim (tarball) + LazyVim Starter

- Neovim is installed from the official tarball into `/opt/nvim-linux-x86_64` (x86_64 only; other archs will error out).
- PATH is set via zsh env: `export PATH="$PATH:/opt/nvim-linux-x86_64/bin"` (managed in `chezmoi/dot_zshenv`).
- LazyVim Starter setup:
- If `~/.config/nvim` already looks like LazyVim (and `.git` is removed), the step is a NOOP.
- Existing Neovim dirs are backed up with `.bak` (timestamped if needed).
- Starter is cloned to `~/.config/nvim` and `.git` is removed.
- Overlay dotfiles from `chezmoi/dot_config/nvim/` are applied via `rsync -a` (no deletions).

## Python + uv

- Python is installed via mise (`python = "latest"` in `mise/mise.toml`).
- uv is installed by `bootstrap/steps/31-python-uv.sh`.
Language runtimes and user-level CLI tools are declared in `mise/mise.toml` and installed together by `bootstrap/steps/30-mise.sh`. Run `mise outdated` to inspect available updates and `mise upgrade` to update the toolchain.

## Dotfiles with chezmoi

Expand All @@ -72,26 +71,19 @@ INSTALL_VSCODE_LINUX=1 ./bootstrap/install.sh
- `chezmoi diff`
- `chezmoi apply`
- `chezmoi update`
- Neovim overlay files live in `chezmoi/dot_config/nvim/` and are layered on top of the LazyVim Starter base.

## Global package lists

- NPM globals live in `bootstrap/packages/npm.txt` and are installed by `bootstrap/steps/36-npm-packages.sh`.
- Cargo globals live in `bootstrap/packages/cargo.txt` and are installed by `bootstrap/steps/35-cargo-packages.sh`.

## Shell quality

- Lint: `./scripts/lint-shell.sh`
- Format: `./scripts/format-shell.sh`
- Prereqs:
- ShellCheck is installed by the bootstrap (or via `sudo apt-get install -y shellcheck`).
- shfmt via Go: `go install mvdan.cc/sh/v3/cmd/shfmt@latest`
- ShellCheck and shfmt are managed by mise; zsh is installed through apt.

## What this installs

- Apt packages: zsh, git, curl, ca-certificates, fzf, ripgrep, fd-find (+ fd symlink), eza, zoxide, rsync, podman, podman-compose
- From official sources/releases: GitHub CLI (apt repo), Oh-My-Zsh, Powerlevel10k theme, zsh-autosuggestions, zsh-syntax-highlighting, mise, lazygit, lazydocker, try, Neovim (tarball to /opt)
- Toolchains via mise: Rust (cargo), Go, .NET SDK, Node.js LTS, Bun
- Apt packages: zsh, git, curl, ca-certificates, build-essential, unzip, rsync
- System/custom sources: Docker Engine, Oh-My-Zsh, Powerlevel10k, zsh-autosuggestions, zsh-syntax-highlighting, mise, try, and optional native VS Code
- Mise toolchain: Node.js LTS, pnpm, Go, Rust, .NET SDK, Bun, Python, uv, Codex, GitHub CLI, lazygit, lazydocker, chezmoi, fzf, ripgrep, fd, eza, fastfetch, ShellCheck, and shfmt

## Usage

Expand All @@ -116,9 +108,8 @@ INSTALL_VSCODE_LINUX=1 ./bootstrap/install.sh
./bootstrap/install.sh
```
- Targeted updates:
- `mise upgrade`
- `chezmoi apply`
- `./bootstrap/steps/36-npm-packages.sh`
- `./bootstrap/steps/35-cargo-packages.sh`

If the installer is not executable, either run:

Expand Down
10 changes: 2 additions & 8 deletions bootstrap/install.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,11 @@ log "Starting bootstrap"

steps=(
"00-preflight.sh"
"05-apt-ppa.sh"
"10-apt.sh"
"20-zsh-ohmyzsh.sh"
"30-mise.sh"
"31-python-uv.sh"
"35-cargo-packages.sh"
"36-npm-packages.sh"
"40-podman.sh"
"50-tools-github.sh"
"50-neovim.sh"
"55-lazyvim.sh"
"40-docker.sh"
"50-try.sh"
"60-dotfiles.sh"
"70-vscode.sh"
"90-doctor.sh"
Expand Down
29 changes: 26 additions & 3 deletions bootstrap/lib/common.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,24 @@ need_cmd() {
}

is_wsl() {
grep -qi microsoft /proc/version 2>/dev/null || [ -n "${WSL_INTEROP:-}" ] || [ -n "${WSL_DISTRO_NAME:-}" ]
[ -n "${WSL_INTEROP:-}" ] ||
[ -n "${WSL_DISTRO_NAME:-}" ] ||
{
grep -qi microsoft /proc/sys/kernel/osrelease 2>/dev/null &&
{
[ -d /run/WSL ] ||
[ -e /proc/sys/fs/binfmt_misc/WSLInterop ] ||
[ -d /mnt/c/Windows ]
}
}
}

platform_name() {
if is_wsl; then
printf 'WSL'
else
printf 'native Ubuntu'
fi
}

is_ubuntu() {
Expand Down Expand Up @@ -55,8 +72,14 @@ link_file() {
src="$1"
dest="$2"

if [ -L "${dest}" ] && [ "$(readlink "${dest}")" = "${src}" ]; then
log "Link exists: ${dest}"
if [ -L "${dest}" ]; then
if [ "$(readlink "${dest}")" = "${src}" ]; then
log "Link exists: ${dest}"
return 0
fi

ln -sfn "${src}" "${dest}"
log "Updated link: ${dest} -> ${src}"
return 0
fi

Expand Down
3 changes: 0 additions & 3 deletions bootstrap/packages/cargo.txt

This file was deleted.

3 changes: 0 additions & 3 deletions bootstrap/packages/npm.txt

This file was deleted.

15 changes: 10 additions & 5 deletions bootstrap/steps/00-preflight.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ if ! is_ubuntu; then
fi

version_id="$(ubuntu_version_id)"
if [ "${version_id}" != "24.04" ]; then
die "This installer targets Ubuntu 24.04. Detected: ${version_id}."
if [ "${version_id}" != "26.04" ]; then
die "This installer targets Ubuntu 26.04. Detected: ${version_id}."
fi

log "Detected $(platform_name) on Ubuntu ${version_id}"

if is_wsl; then
if [ ! -d /run/systemd/system ]; then
warn "WSL detected, but systemd is not running. Using rootless Docker session fallback."
cat >&2 <<'EOM'
[ERROR] WSL detected, but systemd is not running.
Enable systemd in /etc/wsl.conf:
[WARN] For systemd user services (recommended), enable systemd in /etc/wsl.conf:

[boot]
systemd=true
Expand All @@ -35,8 +37,11 @@ Then run from Windows:

Re-open your WSL distro and rerun the installer.
EOM
exit 1
else
log "WSL systemd is available"
fi
elif [ ! -d /run/systemd/system ]; then
warn "Native Ubuntu detected without systemd; service setup will use available fallbacks."
fi

log "Preflight OK"
26 changes: 0 additions & 26 deletions bootstrap/steps/05-apt-ppa.sh

This file was deleted.

18 changes: 2 additions & 16 deletions bootstrap/steps/10-apt.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,13 @@ packages=(
git
curl
ca-certificates
gcc
fzf
ripgrep
fd-find
eza
zoxide
build-essential
unzip
rsync
shellcheck
fastfetch
podman
podman-compose
)

log "Updating apt metadata"
sudo apt-get update

log "Installing apt packages"
sudo apt-get install -y "${packages[@]}"

if have_cmd fdfind && ! have_cmd fd; then
mkdir -p "${HOME}/.local/bin"
ln -sf "$(command -v fdfind)" "${HOME}/.local/bin/fd"
log "Linked fd -> fdfind in ~/.local/bin"
fi
Empty file modified bootstrap/steps/20-zsh-ohmyzsh.sh
100644 → 100755
Empty file.
56 changes: 55 additions & 1 deletion bootstrap/steps/30-mise.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,59 @@ else
yes | mise trust "${config_dest}"
fi

log "Installing mise toolchains"
log "Installing mise toolchains and CLI tools"
MISE_CONFIG_FILE="${config_src}" mise install

retired_mise_tools=(
neovim
zoxide
)

for tool in "${retired_mise_tools[@]}"; do
if mise ls --installed --no-header "${tool}" 2>/dev/null | grep -q "^${tool}[[:space:]]"; then
log "Removing retired mise tool: ${tool}"
mise uninstall --all --yes "${tool}"
fi
done

legacy_apt_packages=(
fzf
ripgrep
fd-find
eza
zoxide
shellcheck
shfmt
fastfetch
neovim
gh
)

installed_legacy_packages=()
for package in "${legacy_apt_packages[@]}"; do
if dpkg -s "${package}" >/dev/null 2>&1; then
installed_legacy_packages+=("${package}")
fi
done

if [ "${#installed_legacy_packages[@]}" -gt 0 ]; then
log "Removing obsolete apt tool copies: ${installed_legacy_packages[*]}"
sudo apt-get remove -y "${installed_legacy_packages[@]}"
fi

sudo rm -f \
/etc/apt/sources.list.d/github-cli.list \
/usr/share/keyrings/githubcli-archive-keyring.gpg

for binary in chezmoi lazygit lazydocker uv uvx; do
legacy_binary="${HOME}/.local/bin/${binary}"
if [ -e "${legacy_binary}" ] || [ -L "${legacy_binary}" ]; then
rm -f "${legacy_binary}"
log "Removed legacy binary: ${legacy_binary}"
fi
done

if MISE_CONFIG_FILE="${config_src}" mise exec -- pnpm list -g --depth=0 @openai/codex >/dev/null 2>&1; then
log "Removing pnpm copy of @openai/codex"
MISE_CONFIG_FILE="${config_src}" mise exec -- pnpm remove -g @openai/codex
fi
20 changes: 0 additions & 20 deletions bootstrap/steps/31-python-uv.sh

This file was deleted.

Loading
Loading