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
4 changes: 4 additions & 0 deletions agents/skills/mise-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Read this before adding or changing a mise-managed tool. Default tools belong in `etc/mise/conf.d/omarchy-tools.toml`, which the settings package installs; `install/user/mise.sh` creates their native mise shims during user setup.

## Leave the user's config alone

Omarchy does not write `~/.config/mise/config.toml`; it is the user's, for overriding what Omarchy ships. Settings go in `etc/mise/conf.d/omarchy.toml`, which still loads after a user opts out of the default tools, and tools in `omarchy-tools.toml`. A command that installs a default tool runs `mise install <tool>` when `omarchy-mise-default-tools declares <tool>`, and `mise use -g` only for a user who opted out. `mise use -g` on a declared tool would copy the declaration's options, `lazy = true` included, into the user's config.

## Declare a default tool

Use a registry shorthand and set `lazy = true` so the first command invocation installs the tool. Follow the existing defaults' release policy: `minimum_release_age = "0s"` makes new releases eligible immediately.
Expand Down
8 changes: 7 additions & 1 deletion bin/omarchy-install-dev-env
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ install_php() {

install_node() {
echo -e "Installing Node.js...\n"
mise use --global node
# Omarchy declares Node as a default tool. A user who removed the preinstalls,
# like one without Omarchy's declarations, selects it in their own config.
if omarchy-mise-default-tools declares node; then
mise install node
else
mise use --global node
fi
}

case "$1" in
Expand Down
12 changes: 5 additions & 7 deletions bin/omarchy-provision-owner
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
# Runs on tty1 via omarchy-provision-owner.service when /var/lib/omarchy/provisioning/pending
# exists — the state a deferred-provisioning ISO install or omarchy-system-factory-reset leaves
# behind. Asks for the user (the configurator's Step 2), creates it with the
# groups system setup recorded, finalizes it offline from the stashed Node
# tarball, re-keys LUKS from the throwaway install passphrase to the user's
# password, and hands off to SDDM. A platform whose boot package owns its boot
# chain prepares it, drops the install key's unlock from it and records the
# kept key slot through omarchy-lifecycle-dispatch.
# groups system setup recorded, finalizes it offline, re-keys LUKS from the
# throwaway install passphrase to the user's password, and hands off to SDDM.
# A platform whose boot package owns its boot chain prepares it, drops the
# install key's unlock from it and records the kept key slot through
# omarchy-lifecycle-dispatch.

set -euo pipefail

Expand Down Expand Up @@ -1123,8 +1123,6 @@ reset_limine_config() {
}

cleanup_oem_state() {
# Keep groups + packages: omarchy-system-factory-reset stages the bundled Node
# tarball from these live copies when the factory snapshot predates it.
rm -f "$PROVISIONING_DIR/pending" "$PROVISIONING_DIR/authorized_keys" "$PROVISIONING_DIR/setup-user" "$REKEY_STATE"

rm -f /etc/systemd/system/multi-user.target.wants/omarchy-provision-owner.service
Expand Down
8 changes: 0 additions & 8 deletions bin/omarchy-system-factory-reset
Original file line number Diff line number Diff line change
Expand Up @@ -518,14 +518,6 @@ stage_full_reset() {
# reset would restore that account. Scrub it once, in place.
sanitize_factory_baseline "$top/@factory"

# Keep the Node tarball reachable for offline first-boot finalization.
if ! compgen -G "$next$PROVISIONING_DIR/packages/node-v*.tar.gz" >/dev/null; then
if compgen -G "$PROVISIONING_DIR/packages/node-v*.tar.gz" >/dev/null; then
install -d -m 755 "$next$PROVISIONING_DIR/packages"
cp "$PROVISIONING_DIR"/packages/node-v*.tar.gz "$next$PROVISIONING_DIR/packages/"
fi
fi

install -d -m 755 "$next$PROVISIONING_DIR"
touch "$next$PROVISIONING_DIR/pending" "$next$PROVISIONING_DIR/wipe-pending"

Expand Down
1 change: 1 addition & 0 deletions etc/mise/conf.d/omarchy-tools.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ grok = { version = "latest", lazy = true, auto_update = "4h", minimum_release_ag
hey-cli = { version = "latest", lazy = true, minimum_release_age = "0s" }
hunk = { version = "latest", lazy = true, minimum_release_age = "0s" }
muse = { version = "latest", lazy = true, auto_update = "4h", minimum_release_age = "0s" }
node = { version = "latest", lazy = true, minimum_release_age = "0s" }
"npm:@moonshot-ai/kimi-code" = { version = "latest", lazy = true, auto_update = "4h", lazy_bins = ["kimi"], minimum_release_age = "0s" }
"npm:cf" = { version = "latest", lazy = true, lazy_bins = ["cf"], minimum_release_age = "0s" }
oh-my-pi = { version = "latest", lazy = true, auto_update = "4h", minimum_release_age = "0s" }
Expand Down
5 changes: 5 additions & 0 deletions etc/mise/conf.d/omarchy.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Upgrades must not delete the version a running process is executing from:
# mise up would prune the old install dir out from under a live session.
[settings]
upgrade.auto_prune = false

# Keep Cursor optional while applying these options whenever mise installs it.
# Expose only its launcher so the bundled Node cannot shadow the user's Node.
[tool_alias]
Expand Down
43 changes: 0 additions & 43 deletions install/user/mise-work.sh
Original file line number Diff line number Diff line change
@@ -1,46 +1,3 @@
# Setup default work directory (and tries)
mkdir -p "$HOME/Work"
mkdir -p "$HOME/Work/tries"

# Offline installs unpack the Node tarball bundled by the ISO: from
# /opt/packages in the ISO chroot, or from the copy staged in provisioning state when
# omarchy-provision-owner finalizes the user at first boot.
case ${OMARCHY_SETUP_CONTEXT:-runtime} in
iso-chroot) NODE_PACKAGE_DIR=/opt/packages ;;
provision-owner) NODE_PACKAGE_DIR=/var/lib/omarchy/provisioning/packages ;;
*) NODE_PACKAGE_DIR="" ;;
esac

if [[ -n $NODE_PACKAGE_DIR ]]; then
# The ISO bundles the tarball for its own architecture.
case $(uname -m) in
aarch64) NODE_PLATFORM=linux-arm64 ;;
*) NODE_PLATFORM=linux-x64 ;;
esac
NODE_TARBALL=$(find "$NODE_PACKAGE_DIR" -name "node-v*-$NODE_PLATFORM.tar.gz" -type f 2>/dev/null | head -n1)
if [[ -z $NODE_TARBALL ]]; then
if [[ ${OMARCHY_SETUP_CONTEXT:-} == "provision-owner" ]]; then
# A factory snapshot predating the bundled tarball may not have it staged.
# Leave Node to the network rather than failing the whole first boot.
echo "Warning: no bundled Node.js tarball in $NODE_PACKAGE_DIR; trying the network" >&2
mise use -g node@latest || echo "Warning: Node.js install deferred (no network)" >&2
else
echo "Error: bundled Node.js tarball missing from $NODE_PACKAGE_DIR" >&2
exit 1
fi
else
NODE_VERSION=$(basename "$NODE_TARBALL" | sed "s/node-v\(.*\)-$NODE_PLATFORM.tar.gz/\1/")
NODE_INSTALL_DIR="$HOME/.local/share/mise/installs/node/$NODE_VERSION"

mkdir -p "$NODE_INSTALL_DIR"
tar -xzf "$NODE_TARBALL" --strip-components=1 -C "$NODE_INSTALL_DIR"
mise use -g node@"$NODE_VERSION"

# That pinned the exact bundled version, which would exempt Node from
# mise up forever. Loosen it to latest, like an online install gets:
# mise resolves latest to the installed version while offline.
mise config set tools.node latest --file "$HOME/.config/mise/config.toml"
fi
else
mise use -g node@latest
fi
4 changes: 0 additions & 4 deletions install/user/mise.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# Upgrades must not delete the version a running process is executing from:
# mise up would prune the old install dir out from under a live session.
mise settings set upgrade.auto_prune false

# Build the lazy shims for Omarchy's default tools and the account dispatchers,
# both declared by /etc/mise/conf.d.
mise reshim --system
Expand Down
10 changes: 10 additions & 0 deletions migrations/1792343099.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
echo "Leave Node to Omarchy's mise declaration instead of the user's config"

# Setup used to write `node = "latest"` into the user's mise config. Node now
# ships as a lazy default in /etc/mise/conf.d, and that entry would outrank it.
# Drop only the exact latest request, since a pinned version is the user's
# choice, and keep the install for the declaration to reuse. After an opt-out
# the entry is what still provides Node, so it stays.
if omarchy-mise-default-tools declares node; then
mise unuse -g --no-prune node@latest
fi
12 changes: 12 additions & 0 deletions migrations/1792343225.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
echo "Leave mise's upgrade pruning setting to Omarchy's config instead of the user's"

# Setup and an earlier migration wrote upgrade.auto_prune = false into the
# user's mise config. The settings package ships it in /etc/mise/conf.d now, so
# that copy only clutters a file that is the user's. A different value is the
# user's choice and stays. The file is mise's global config, found the way
# mise finds it, since that's where `mise settings set` wrote.
mise_config=${MISE_GLOBAL_CONFIG_FILE:-${MISE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/mise}/config.toml}

if [[ -f $mise_config ]] && [[ $(mise config get -f "$mise_config" settings.upgrade.auto_prune 2>/dev/null) == "false" ]]; then
mise settings unset upgrade.auto_prune
fi
8 changes: 5 additions & 3 deletions test/shell.d/default-agent-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,11 @@ for tool in codex claude copilot opencode pi "\"$kimi_package\""; do
fail "user setup declares $tool as a self-updating native lazy tool"
done
[[ $(grep -c 'auto_update = "4h"' "$lazy_config") == 12 ]] || fail "only the agent CLIs update themselves"
grep -Eq '^uv = \{ version = "latest", lazy = true, minimum_release_age = "0s" \}$' "$lazy_config" ||
fail "user setup declares uv as a native lazy tool"
[[ $(grep -c 'lazy = true' "$lazy_config") == 21 ]] || fail "user setup declares every default mise tool as lazy"
for tool in node uv; do
grep -Eq "^$tool = \\{ version = \"latest\", lazy = true, minimum_release_age = \"0s\" \\}$" "$lazy_config" ||
fail "user setup declares $tool as a native lazy tool"
done
[[ $(grep -c 'lazy = true' "$lazy_config") == 22 ]] || fail "user setup declares every default mise tool as lazy"
grep -Fx "reshim --system" "$mise_history" >/dev/null || fail "user setup builds the system lazy bootstrap shims"
pass "user setup configures system lazy tools from registry shorthands"
: >"$stub_log"
Expand Down
6 changes: 2 additions & 4 deletions test/shell.d/factory-reset-accounts-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,9 @@ make_fixture() {
local top="$1" root_hash="${2:-original-root-hash}"
local factory="$top/@factory"
mkdir -p "$top/@" "$factory/etc" "$factory/home/seller" \
"$factory/usr/bin" "$factory/usr/share/omarchy/install/provisioning" \
"$factory/var/lib/omarchy/provisioning/packages"
"$factory/usr/bin" "$factory/usr/share/omarchy/install/provisioning"
touch "$top/@/old-system" "$factory/home/seller/private-file" \
"$factory/usr/share/omarchy/install/provisioning/omarchy-provision-owner.service" \
"$factory/var/lib/omarchy/provisioning/packages/node-v0.tar.gz"
"$factory/usr/share/omarchy/install/provisioning/omarchy-provision-owner.service"
printf '#!/bin/bash\n' >"$factory/usr/bin/omarchy-provision-owner"
chmod +x "$factory/usr/bin/omarchy-provision-owner"
printf 'true\n' >"$factory/read-only"
Expand Down
90 changes: 90 additions & 0 deletions test/shell.d/mise-auto-prune-migration-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

require_command mise

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
migration="$ROOT/migrations/1792343225.sh"

# The settings package ships the setting before migrations run.
system_dir="$test_tmp/etc/mise"
mkdir -p "$system_dir"
cp -r "$ROOT/etc/mise/conf.d" "$system_dir/"

in_home() {
local test_home="$1"
shift

(
cd "$test_home"
env -i \
HOME="$test_home" \
XDG_CACHE_HOME="$test_home/.cache" \
XDG_CONFIG_HOME="$test_home/.config" \
XDG_DATA_HOME="$test_home/.local/share" \
XDG_STATE_HOME="$test_home/.local/state" \
MISE_SYSTEM_DIR="$system_dir" \
PATH="$ROOT/bin:/usr/bin" \
"$@"
)
}

new_home() {
local test_home="$test_tmp/$1"
mkdir -p "$test_home/.config/mise"
printf '%s' "$2" >"$test_home/.config/mise/config.toml"
echo "$test_home"
}

legacy_home=$(new_home legacy $'[tools]\nruby = "latest"\n\n[settings]\nminimum_release_age = "0s"\n\n[settings.upgrade]\nauto_prune = false\n')
legacy_config="$legacy_home/.config/mise/config.toml"
in_home "$legacy_home" bash -euo pipefail "$migration" >/dev/null
grep -q 'auto_prune' "$legacy_config" && fail "migration drops the setting setup wrote"
grep -Fx 'ruby = "latest"' "$legacy_config" >/dev/null || fail "migration keeps the user's tools"
grep -Fx 'minimum_release_age = "0s"' "$legacy_config" >/dev/null || fail "migration keeps the user's other settings"
[[ $(in_home "$legacy_home" mise settings get upgrade.auto_prune 2>/dev/null) == "false" ]] ||
fail "upgrades still keep versions in use once the user's copy is gone"
cp "$legacy_config" "$test_tmp/legacy-after"
in_home "$legacy_home" bash -euo pipefail "$migration" >/dev/null
cmp -s "$test_tmp/legacy-after" "$legacy_config" || fail "migration is idempotent"
pass "migration hands upgrade pruning over to Omarchy's config"

dotted_home=$(new_home dotted $'[settings]\nupgrade.auto_prune = false\n')
in_home "$dotted_home" bash -euo pipefail "$migration" >/dev/null
grep -q 'auto_prune' "$dotted_home/.config/mise/config.toml" && fail "migration drops a dotted-key copy of the setting"
pass "migration finds the setting in either TOML form"

# mise keeps its global config under XDG_CONFIG_HOME, so that's where setup wrote.
xdg_home=$(new_home xdg "")
xdg_config="$xdg_home/xdg/mise/config.toml"
mkdir -p "$(dirname "$xdg_config")"
printf '[settings.upgrade]\nauto_prune = false\n' >"$xdg_config"
in_home "$xdg_home" env XDG_CONFIG_HOME="$xdg_home/xdg" bash -euo pipefail "$migration" >/dev/null
grep -q 'auto_prune' "$xdg_config" && fail "migration drops the setting from a config under XDG_CONFIG_HOME"
pass "migration finds the config where XDG_CONFIG_HOME puts it"

chosen_home=$(new_home chosen $'[settings.upgrade]\nauto_prune = true\n')
cp "$chosen_home/.config/mise/config.toml" "$test_tmp/chosen-before"
in_home "$chosen_home" bash -euo pipefail "$migration" >/dev/null
cmp -s "$test_tmp/chosen-before" "$chosen_home/.config/mise/config.toml" || fail "migration keeps a value the user chose"
pass "migration keeps a pruning choice the user made"

# A config kept with dotfiles is often a link into the user's repository.
linked_home=$(new_home linked "")
dotfile="$test_tmp/dotfiles-mise.toml"
printf '[settings.upgrade]\nauto_prune = false\n' >"$dotfile"
ln -sf "$dotfile" "$linked_home/.config/mise/config.toml"
in_home "$linked_home" bash -euo pipefail "$migration" >/dev/null
[[ -L $linked_home/.config/mise/config.toml ]] || fail "migration preserves a dotfile symlink"
grep -q 'auto_prune' "$dotfile" && fail "migration edits the dotfile the link points to"
pass "migration edits a linked config in place"

empty_home="$test_tmp/empty"
mkdir -p "$empty_home"
in_home "$empty_home" bash -euo pipefail "$migration" >/dev/null || fail "migration succeeds without a user mise config"
[[ ! -e $empty_home/.config/mise/config.toml ]] || fail "migration does not create a user mise config"
pass "migration leaves a missing user config missing"
49 changes: 49 additions & 0 deletions test/shell.d/mise-node-dev-env-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

mock_bin="$test_tmp/bin"
test_home="$test_tmp/home"
mise_log="$test_tmp/mise"
mkdir -p "$mock_bin" "$test_home"

cat >"$mock_bin/mise" <<'SH'
#!/bin/bash
printf '%s\n' "$*" >>"$OMARCHY_TEST_MISE_LOG"
SH
chmod +x "$mock_bin/mise"

export HOME="$test_home"
export OMARCHY_TEST_MISE_LOG="$mise_log"
export PATH="$mock_bin:$ROOT/bin:$PATH"

mise_config="$test_tmp/etc/mise/config.toml"
mkdir -p "$(dirname "$mise_config")"
cp "$ROOT/etc/mise/conf.d/omarchy-tools.toml" "$mise_config"
export OMARCHY_MISE_CONFIG_PATH="$mise_config"

bash "$ROOT/bin/omarchy-install-dev-env" node >/dev/null
[[ $(<"$mise_log") == "install node" ]] || fail "Node setup installs the system-configured Node and leaves the user config alone"
pass "Node setup installs Omarchy's declared Node"

# mise skips Omarchy's declarations for a user who removed the preinstalls, so
# Node goes in their own config, leaving the opt-out in place.
miserc="$test_home/.config/mise/miserc.toml"
mkdir -p "$(dirname "$miserc")"
printf 'ignored_config_paths = ["%s"]\n' "$mise_config" >"$miserc"
: >"$mise_log"
bash "$ROOT/bin/omarchy-install-dev-env" node >/dev/null
[[ $(<"$mise_log") == "use --global node" ]] || fail "Node setup selects Node after Remove Preinstalls"
rm "$miserc"
pass "Node setup selects Node after Remove Preinstalls"

rm "$mise_config"
: >"$mise_log"
bash "$ROOT/bin/omarchy-install-dev-env" node >/dev/null
[[ $(<"$mise_log") == "use --global node" ]] || fail "Node setup selects Node when no system config declares it"
pass "Node setup selects Node without Omarchy's declarations"
Loading
Loading