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
1 change: 1 addition & 0 deletions .chezmoiroot
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chezmoi
32 changes: 32 additions & 0 deletions .github/workflows/shell-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Shell Quality

on:
push:
branches:
- main
pull_request:

jobs:
shell-quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

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

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable

- name: Install shfmt
run: |
go install mvdan.cc/sh/v3/cmd/shfmt@latest
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Lint shell
run: |
chmod +x ./scripts/lint-shell.sh
./scripts/lint-shell.sh
38 changes: 0 additions & 38 deletions .gitignore

This file was deleted.

93 changes: 92 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
# dotfiles
# Ubuntu 24.04 Developer Bootstrap (WSL2 + native)

Reproducible, idempotent bootstrap for Ubuntu 24.04 that installs a full developer distro and applies dotfiles via chezmoi.

## One-liner install

Replace `<REPO_URL>` with your GitHub repo URL:

```bash
git clone <REPO_URL> ~/.dotfiles && ~/.dotfiles/bootstrap/install.sh
```

## WSL2 systemd requirement (mandatory for Podman rootless)

Podman rootless socket activation uses `systemctl --user`, which requires systemd. If running in WSL2:

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

```
[boot]
systemd=true
```

2. From Windows, run:

```powershell
wsl.exe --shutdown
```

3. Re-open your WSL distro and rerun the installer.

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

## VS Code on WSL (recommended)

- Install VS Code on Windows (not inside WSL).
- During install, enable "Add to PATH".
- Install the "Remote Development" extension pack (or at minimum the WSL extension).
- From WSL, run: `code .` — this opens the folder in VS Code and installs the VS Code Server in WSL automatically.

## VS Code on Ubuntu (optional)

Native Linux VS Code is opt-in. Set the environment variable to install via the official Microsoft apt repo:

```bash
INSTALL_VSCODE_LINUX=1 ./bootstrap/install.sh
```

## Dotfiles with chezmoi

- Source state lives in `chezmoi/` (enabled via `.chezmoiroot`).
- The installer runs `chezmoi init --apply <repo>` on first run, then `chezmoi apply` on subsequent runs.
- Daily operations:
- `chezmoi diff`
- `chezmoi apply`
- `chezmoi update`

## 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`

## What this installs

- Apt packages: zsh, git, curl, ca-certificates, fzf, ripgrep, fd-find (+ fd symlink), eza, zoxide, 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
- Toolchains via mise: Rust (cargo), Go, .NET SDK, Node.js LTS, Bun

## Usage

- Run full bootstrap:
```bash
./bootstrap/install.sh
```
- Run doctor checks only:
```bash
./bootstrap/steps/90-doctor.sh
```

## Notes & assumptions

- Ubuntu 24.04 only. Other distros/versions are rejected by preflight.
- WSL best practice: keep repos inside the Linux filesystem (e.g. `~/src`), not `/mnt/c`.
- Dotfiles are managed by chezmoi; re-running the installer reapplies the repo state.
- After opening a new shell, run `p10k configure` to personalize the Powerlevel10k prompt.
- Neovim and lazygit use GitHub “latest release” assets resolved at install time.
- `zsh-syntax-highlighting` must remain last in the plugins list; this repo enforces that ordering.
- The mise installer URL is assumed to be `https://mise.run` (official). Update it if upstream changes.
- For deterministic toolchains, pin exact versions in `mise/mise.toml`. A `mise.lock` can be generated and committed using mise tooling when needed.
32 changes: 32 additions & 0 deletions bootstrap/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

BOOTSTRAP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export BOOTSTRAP_ROOT

# shellcheck source=bootstrap/lib/common.sh
. "${BOOTSTRAP_ROOT}/bootstrap/lib/common.sh"

log "Starting bootstrap"

steps=(
"00-preflight.sh"
"10-apt.sh"
"20-zsh-ohmyzsh.sh"
"30-mise.sh"
"40-podman.sh"
"50-tools-github.sh"
"70-vscode.sh"
"60-dotfiles.sh"
"90-doctor.sh"
)

for step in "${steps[@]}"; do
log "Running ${step}"
bash "${BOOTSTRAP_ROOT}/bootstrap/steps/${step}"
log "Completed ${step}"
echo
done

log "Bootstrap finished"
71 changes: 71 additions & 0 deletions bootstrap/lib/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

log() {
printf '[INFO] %s\n' "$*"
}

warn() {
printf '[WARN] %s\n' "$*" >&2
}

die() {
printf '[ERROR] %s\n' "$*" >&2
exit 1
}

have_cmd() {
command -v "$1" >/dev/null 2>&1
}

need_cmd() {
have_cmd "$1" || die "Missing command: $1"
}

is_wsl() {
grep -qi microsoft /proc/version 2>/dev/null || [ -n "${WSL_INTEROP:-}" ] || [ -n "${WSL_DISTRO_NAME:-}" ]
}

is_ubuntu() {
[ -r /etc/os-release ] || return 1
# shellcheck disable=SC1091
. /etc/os-release
[ "${ID:-}" = "ubuntu" ]
}

ubuntu_version_id() {
[ -r /etc/os-release ] || return 1
# shellcheck disable=SC1091
. /etc/os-release
printf '%s' "${VERSION_ID:-}"
}

download_to_tmp() {
local url tmp
url="$1"
tmp="$(mktemp)"
log "Downloading ${url}"
curl -fsSL "${url}" -o "${tmp}"
printf '%s' "${tmp}"
}

link_file() {
local src dest
src="$1"
dest="$2"

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

if [ -e "${dest}" ]; then
warn "Skip existing path: ${dest}"
return 0
fi

mkdir -p "$(dirname "${dest}")"
ln -s "${src}" "${dest}"
log "Linked ${dest} -> ${src}"
}
42 changes: 42 additions & 0 deletions bootstrap/steps/00-preflight.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
# shellcheck source=bootstrap/lib/common.sh
. "${BOOTSTRAP_ROOT}/bootstrap/lib/common.sh"

if [ "${EUID:-$(id -u)}" -eq 0 ]; then
die "Run this installer as a regular user (not root)."
fi

need_cmd sudo

if ! is_ubuntu; then
die "This installer supports Ubuntu only."
fi

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

if is_wsl; then
if [ ! -d /run/systemd/system ]; then
cat >&2 <<'EOM'
[ERROR] WSL detected, but systemd is not running.
Enable systemd in /etc/wsl.conf:

[boot]
systemd=true

Then run from Windows:
wsl.exe --shutdown

Re-open your WSL distro and rerun the installer.
EOM
exit 1
fi
fi

log "Preflight OK"
34 changes: 34 additions & 0 deletions bootstrap/steps/10-apt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
# shellcheck source=bootstrap/lib/common.sh
. "${BOOTSTRAP_ROOT}/bootstrap/lib/common.sh"

packages=(
zsh
git
curl
ca-certificates
fzf
ripgrep
fd-find
eza
zoxide
shellcheck
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
Loading
Loading