automate/020_mise.sh

209 lines
6.2 KiB
Bash

#!/usr/bin/env bash
#
# a development environment setup tool.
sudo apt update -qq && sudo apt install -y curl
# sudo install -dm 755 /etc/apt/keyrings
DEST=${1:-/etc/skel}
URI="mise.jdx.dev"
TRANSPORT="https:/"
TYPES=(deb)
SUITES="stable"
COMPONENTS=(main)
ARCHITECTURES=(amd64)
KEY_DIR="/usr/share/keyrings"
KEY="${KEY_DIR}/mise-archive-keyring.gpg"
MISE_HOME="${DEST}/.config/mise"
MISE_CONFIG_DIR="${MISE_HOME}/conf.d"
mkdir -p "${MISE_CONFIG_DIR}"
curl -fSs ${TRANSPORT}/${URI}/gpg-key.pub | sudo gpg --dearmor --yes -o ${KEY} 1>/dev/null
conf_print_sources() {
cat <<EOF
Enabled: yes
Types: ${TYPES[*]}
URIs: ${TRANSPORT}/${URI}/deb/
Suites: ${SUITES}
Components: ${COMPONENTS[*]}
Architectures: ${ARCHITECTURES[*]}
Signed-By: ${KEY}
EOF
}
conf_print_sources | sudo tee /etc/apt/sources.list-available/mise.sources
sudo ln -sf /etc/apt/sources.list-available/mise.sources /etc/apt/sources.list.d/mise.sources
sudo apt update -qq
sudo apt install -y mise
# Helper functions
# Move the previous config to .backup, date and prune
setup_config_file() {
local target_file="$1"
local provider_cmd="$2"
local retention_days=30
local target_dir
target_dir=$(dirname "$target_file")
local backup_dir="${target_dir}/.backups"
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
# 1. Ensure backup directory exists
mkdir -p "$backup_dir"
# 2. Capture new config first to prevent losing the old one if the command fails
local temp_config
temp_config=$(mktemp)
if ! $provider_cmd >"$temp_config"; then
echo "Error: Provider command failed. Keeping existing config."
rm -f "$temp_config"
return 1
fi
# 3. Rotate existing config if it exists
if [[ -f "$target_file" ]]; then
echo "Backing up $(basename "$target_file") to .backups/..."
mv "$target_file" "${backup_dir}/$(basename "$target_file").${timestamp}.bak"
fi
# 4. Move new config into place
mv "$temp_config" "$target_file"
echo "New config written to $target_file"
# 5. Cleanup
find "$backup_dir" -name "*.bak" -type f -mtime +"$retention_days" -delete
}
# Have mise activated by zsh
echo "eval $(/usr/bin/mise activate zsh)" >"${DEST}/.zshrc.d/004_mise.zsh"
# mise plugins
mise plugins install conda
export MISE_CONDA_PYTHON_EXE=/opt/conda/bin/python
# then remove zgenom load bckim92/zsh-autoswitch-conda
# Link mise to existing containers in .mise.yml for a project
CONDA_ENVS_DIR="${DEST}/.conda/envs"
# mise use python@prefix:<path_to_your_conda_env>
# Example if your environment is named 'my-data-env'
# mise use python@prefix:~/anaconda3/envs/my-data-env
# mise use python@prefix:${CONDA_ENVS_DIR}/my-data-env
# mise use node@prefix:${CONDA_ENVS_DIR}/node_env
# mise use go@prefix:${CONDA_ENVS_DIR}/goenv
conf_print_mise_config() {
cat <<EOF
[settings]
experimental = true
always_keep_download = false
plugin_autoupdate_last_check_duration = "7d"
jobs = 4
asdf_compat = true
auto_install = true
[tools]
# --- CORE BINARIES ---
usage = "latest"
fzf = "latest"
uv = "latest"
# --- LANGUAGES ---
python = "3.14"
node = "lts"
rust = "1.94"
go = "1.26"
[env]
EDITOR = "nvim"
GOPATH = "{{ config_root }}/go"
# Ensure Go binaries are available in your shell
PATH = "{{ config_root }}/go/bin:$PATH"
[tasks]
"clean:all" = { run = "mise prune && uv cache clean && cargo clean", description = "Deep clean all package managers" }
"update:tools" = { run = "mise upgrade && rustup update", description = "Upgrade mise tools and rust toolchain" }
"check:health" = { run = "mise doctor && go version && rustc --version", description = "Verify toolchain health" }
# Bonus: A quick setup task for new environments
"setup" = { run = "mise install && uv venv", description = "Install tools and create virtualenv" }
EOF
}
# Usage: setup_config_file "PATH" "GENERATOR_FUNCTION"
setup_config_file "${MISE_HOME}/config.toml" "conf_print_mise_config"
# Post setup
# Install all plugins/tools defined in config
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
sudo chown -R "$(whoami):$(whoami)" ~/.gnupg
mise install
# 📂 Recommended Project Structure
# To keep things organized, I suggest tracking your `source of truth` for
# shorthands inside the repository. This allows you to version control your
# private tool aliases.
# * .github/workflows/mise-health-check.yml: Your CI logic.
# * bin/bootstrap: The script above (remember to chmod +x bin/bootstrap).
# * .mise.shorthands.toml: The actual list of your private my-cli mappings.
# * mise.toml: Your standard tool versions (e.g., python = "3.12", uv = "latest").
# 🚀 GitHub Actions: mise-health-check.yml
# Create this file at .github/workflows/mise-health-check.yml. This workflow
# installs mise, uses it to setup your defined runtimes (Go, Rust, Python,
# Node), and then triggers your check:health task.
conf_print_mise_action() {
cat <<EOF
name: Environment Health Check
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
validate-environment:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Install mise
uses: jdx/mise-action@v2
with:
# This automatically installs tools defined in your mise.toml
install: true
# Optional: cache the mise directory to speed up subsequent runs
cache: true
- name: Verify Tool Versions
run: mise ls --current
- name: Run Project Health Check
# This executes the specific task defined in your mise.toml
run: mise run check:health
- name: Python Environment Lint (uv)
# Ensuring uv is doing its job and the lockfile is synced
run: uv lock --check
if: always()
EOF
}
# 🔍 Why this works for your 2026 stack:
# jdx/mise-action: This is the official action. By setting install: true, it reads
# your mise.toml and ensures every binary (Go, Node, etc.) is present in the
# runner's PATH immediately.
#
# Layered Validation: It doesn't just check if mise works; it runs your actual
# project-level health checks. If a developer forgets to update the uv.lock or a
# Rust crate is missing a system dependency, this job will catch it.
#
# The uv Check: Since you're using uv this ensures the dependencies haven't
# drifted from the pyproject.toml without a lockfile update.