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
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ This repository is organized around the two installer entry points:
- `win/` contains the Windows setup script, `winget` package list, PowerToys
settings backup, and Windows application configuration files.

- `wsl/` contains the WSL setup script and the files it installs into the WSL
environment.
- `wsl/home/` mirrors the target home directory. The installer links these
files into `$HOME`, except for Codex skills, which are copied because Codex
does not discover symlinked skill files.
- `wsl/` contains the WSL launcher and the files installed into the WSL
environment by `mise bootstrap`.
- `wsl/home/` mirrors the target home directory. Mise links these files into
`$HOME`, except for Codex skills, which are copied because Codex does not
discover symlinked skill files.
- `wsl/etc/` mirrors root-owned system files. The installer links these files
into `/etc`.
- `wsl/setup-git.ts` performs interactive GitHub auth and GPG signing setup
Expand All @@ -44,8 +44,8 @@ This repository is organized around the two installer entry points:
`.github/`, such as `rulesets.json`.

- The root configuration files (`mise.toml`, `tasks.toml`, `hk.pkl`, and the
formatter/linter configs) define the development toolchain and checks for both
the root repository and the worker package.
formatter/linter configs) define the machine bootstrap, development toolchain,
and checks for both the root repository and the worker package.

## ⚙️ Installation

Expand Down
36 changes: 36 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ powershell = "7.6.3"

[settings]
experimental = true
dotfiles.default_mode = "symlink"

[settings.npm]
package_manager = "aube"
Expand All @@ -55,6 +56,41 @@ run = "bun install --frozen-lockfile"
[task_config]
includes = ["tasks.toml", "tasks", "worker/tasks.toml", "worker/tasks"]

[bootstrap.packages]
"apt:bubblewrap" = "latest"
"apt:build-essential" = "latest"
"apt:clang" = "latest"
"apt:clangd" = "latest"
"apt:clang-format" = "latest"
"apt:cmake" = "latest"
"apt:containerd.io" = "latest"
"apt:desktop-file-utils" = "latest"
"apt:docker-buildx-plugin" = "latest"
"apt:docker-ce" = "latest"
"apt:docker-ce-cli" = "latest"
"apt:docker-compose-plugin" = "latest"
"apt:ffmpeg" = "latest"
"apt:gdb" = "latest"
"apt:graphviz" = "latest"
"apt:libssl-dev" = "latest"
"apt:llvm" = "latest"
"apt:parallel" = "latest"
"apt:pkg-config" = "latest"
"apt:strace" = "latest"
"apt:xdg-utils" = "latest"
"apt:zip" = "latest"

[dotfiles]
"~/.agents" = { source = "wsl/home/.agents", mode = "copy" }
"~/.bashrc" = "wsl/home/.bashrc"
"~/.codex" = { source = "wsl/home/.codex", mode = "symlink-each" }
"~/.config" = { source = "wsl/home/.config", mode = "symlink-each" }
"~/.ghr" = { source = "wsl/home/.ghr", mode = "symlink-each" }
"~/.gnupg" = { source = "wsl/home/.gnupg", mode = "symlink-each" }
"~/.hushlogin" = "wsl/home/.hushlogin"
"~/.local" = { source = "wsl/home/.local", mode = "symlink-each" }
"~/.selected_editor" = "wsl/home/.selected_editor"

[hooks]
postinstall = """
{% if env.CI is undefined %}
Expand Down
24 changes: 24 additions & 0 deletions tasks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ hk \
"""
description = "Run hk linters and formatters."

[bootstrap]
description = "Finish idempotent WSL user setup after packages, dotfiles, and tools are installed."
run = '''
mkdir --parents "${HOME}/.gnupg"
chmod 700 "${HOME}/.gnupg"

username=$(whoami)
if ! groups "${username}" | grep --quiet --word-regexp docker; then
sudo usermod --append --groups docker "${username}"
fi
Comment on lines +17 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The bootstrap task script uses bash-specific features but does not specify shell = "bash", which can break when run under the default shell.

This script uses bash-specific constructs ([[ -t 0 && -t 1 ]] and bash --interactive -c ...) but the task doesn’t set shell in tasks.toml, so it will run under mise’s default /bin/sh, where [[ ... ]] is invalid. Please either set shell = "bash" for this task or rewrite the conditionals to be POSIX-compliant (e.g., test -t 0, test -t 1, and [ ]).


sudo update-desktop-database

# Installing this before the tools phase makes Git use gh before gh is installed.
ln --symbolic --no-dereference --force \
"${MISE_PROJECT_ROOT}/wsl/home/.gitconfig" "${HOME}/.gitconfig"

if [[ -t 0 && -t 1 ]]; then
if ! bash --interactive -c "${MISE_PROJECT_ROOT}/wsl/setup-git.ts"; then
echo "Git setup failed; run wsl/setup-git.ts manually if needed." >&2
fi
fi
'''

["install:ps-script-analyzer"]
shell = "pwsh -Command"
# Force is required to bypass untrusted repository confirmation
Expand Down
175 changes: 8 additions & 167 deletions wsl/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,8 @@ update_system_packages() {
log_info "System packages updated."
}

install_system_packages() {
log_info "Installing system packages..."
# not pre-installed in wsl ubuntu
# ref: https://cdimages.ubuntu.com/ubuntu-wsl/noble/daily-live/current/noble-wsl-amd64.manifest
sudo apt-get install --yes \
bubblewrap \
build-essential \
clang \
clangd \
clang-format \
cmake \
desktop-file-utils \
ffmpeg \
gdb \
graphviz \
libssl-dev \
llvm \
parallel \
pkg-config \
strace \
xdg-utils \
zip
log_info "Core packages installed."
}

install_custom_registry_packages() {
log_info "Setting up custom APT repositories and installing additional packages..."
log_info "Setting up custom APT repositories and installing mise..."

sudo install --directory --mode=0755 /etc/apt/keyrings

Expand All @@ -83,22 +58,10 @@ install_custom_registry_packages() {
echo "deb [arch=${arch} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${codename} stable" |
sudo tee /etc/apt/sources.list.d/docker.list >/dev/null

log_info "All repositories set up. Installing packages..."
log_info "All repositories set up. Installing mise..."
sudo apt-get update
sudo apt-get install --yes containerd.io docker-buildx-plugin docker-ce docker-ce-cli docker-compose-plugin mise
log_info "Additional packages installed."
}

configure_docker_group() {
log_info "Configuring Docker group..."
local username
username=$(whoami)
if groups "${username}" | grep --quiet --word-regexp 'docker'; then
log_info "User ${username} is already in the docker group."
else
sudo usermod --append --groups docker "${username}"
log_info "User ${username} added to the docker group."
fi
sudo apt-get install --yes mise
log_info "mise installed."
}

checkout_default_git_branch() {
Expand Down Expand Up @@ -195,55 +158,6 @@ clone_or_update_dotfiles_repo() {
echo "${dotfiles_target_dir}"
}

create_home_symlinks() {
local wsl_config_dir="$1"
local repo_root
repo_root="$(realpath "${wsl_config_dir}/..")"
local wsl_home_config_dir
wsl_home_config_dir="$(realpath "${wsl_config_dir}/home")"

log_info "Creating symbolic links for home directory files from ${wsl_home_config_dir}..."
# Postpone .gitconfig
local wsl_home_config_relative_dir
wsl_home_config_relative_dir="$(realpath --relative-to="${repo_root}" "${wsl_home_config_dir}")"
local home_paths_file
home_paths_file="$(mktemp)"
git -C "${repo_root}" ls-files -z -- "${wsl_home_config_relative_dir}" >"${home_paths_file}"
local home_paths=()
while IFS= read -r -d '' path; do
if [[ ${path} == "${wsl_home_config_relative_dir}/.gitconfig" ]]; then
continue
fi
home_paths+=("${repo_root}/${path}")
done <"${home_paths_file}"
rm -- "${home_paths_file}"

if ((${#home_paths[@]} == 0)); then
log_error "No home directory files found to symlink."
exit 1
fi

for path in "${home_paths[@]}"; do
local target_name
local full_path
full_path=$(realpath "${path}")
target_name="$(realpath --relative-to="${wsl_home_config_dir}" "${full_path}")"
local target_path="${HOME}/${target_name}"

mkdir --parents "$(dirname "${target_path}")"
if [[ ${target_name} == .agents/skills/* ]]; then
# Codex skips symlinked skill files during skill discovery.
cp --preserve=mode --remove-destination "${full_path}" "${target_path}"
# shellcheck disable=SC2088 # intentionally print ~ instead of $HOME
log_info "Installed file: ~/${target_name}"
else
ln --symbolic --no-dereference --force "${full_path}" "${target_path}"
# shellcheck disable=SC2088 # intentionally print ~ instead of $HOME
log_info "Installed symlink: ~/${target_name}"
fi
done
}

create_etc_symlinks() {
local wsl_config_dir="$1"
local repo_root
Expand Down Expand Up @@ -282,93 +196,20 @@ create_etc_symlinks() {
done
}

install_mise_tools() {
log_info "Installing global mise tools..."

mise trust --all

log_info "Installing tools..."
mise install --yes --locked
log_info "mise tools installed from lockfile."
}

symlink_gitconfig() {
local wsl_config_dir="$1"
local gitconfig_source="${wsl_config_dir}/home/.gitconfig"
local gitconfig_target="${HOME}/.gitconfig"

log_info "Symlinking .gitconfig from ${gitconfig_source} to ${gitconfig_target}..."
ln --symbolic --no-dereference --force "${gitconfig_source}" "${gitconfig_target}"
# shellcheck disable=SC2088 # intentionally print ~ instead of $HOME
log_info "Installed symlink for ~/.gitconfig"
}

init_gnupg_dir() {
local gnupg_home
gnupg_home="${HOME}/.gnupg"

log_info "Initializing GnuPG directory: ${gnupg_home}"

mkdir -p "${gnupg_home}"
chmod 700 "${gnupg_home}"

log_info "GnuPG directory created."
}

update_desktop_database() {
log_info "Updating desktop database..."
sudo update-desktop-database
log_info "Desktop database updated."
}

run_git_setup_script() {
local dotfiles_repo_root_path="$1"
local setup_script_path="${dotfiles_repo_root_path}/wsl/setup-git.ts"

# Skip if running in non-interactive shell
if [[ ! $- =~ i ]]; then
log_info "Non-interactive shell detected. Skipping git setup script: ${setup_script_path}"
return
fi

log_info "Sourcing .bashrc to set up environment for git setup script..."

# Source the .bashrc to use mise tools and xdg-open
# shellcheck source=wsl/home/.bashrc
source "${HOME}/.bashrc"

log_info "Running git setup script: ${setup_script_path}"

if "${setup_script_path}"; then
log_info "Git setup script completed successfully."
else
log_error "Failed to run ${setup_script_path}. Please run it manually if needed."
fi
}

main() {
update_system_packages
install_system_packages
install_custom_registry_packages
configure_docker_group

local dotfiles_dir
dotfiles_dir=$(clone_or_update_dotfiles_repo "${repo_name}" "${git_ref}")

local wsl_config_dir="${dotfiles_dir}/wsl"
create_home_symlinks "${wsl_config_dir}"
create_etc_symlinks "${wsl_config_dir}"

install_mise_tools

# Postpone .gitconfig to avoid authentication errors in install_mise_tools
symlink_gitconfig "${wsl_config_dir}"

init_gnupg_dir

update_desktop_database

run_git_setup_script "${dotfiles_dir}"
log_info "Bootstrapping packages, dotfiles, and tools with mise..."
mise trust --yes "${dotfiles_dir}/mise.toml"
mise --cd "${dotfiles_dir}" bootstrap --yes --update --force-dotfiles --locked
Comment on lines +210 to +211

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The new mise bootstrap call does not invoke the bootstrap task you added in tasks.toml, so the post-setup steps are no longer executed.

Those post-setup actions were moved into the [bootstrap] task in tasks.toml, but mise ... bootstrap calls the built-in subcommand (using mise.toml), not that task. As a result, your new post-bootstrap steps won’t run. You likely want to invoke the task runner instead, e.g. mise --cd "${dotfiles_dir}" run bootstrap, or move all bootstrap behavior into mise.toml so there’s a single, correctly executed source of truth.

log_info "mise bootstrap completed."

checkout_default_git_branch "${dotfiles_dir}"

Expand Down