diff --git a/.chezmoi.toml.tmpl b/.chezmoi.toml.tmpl index d36f76f..a78e103 100644 --- a/.chezmoi.toml.tmpl +++ b/.chezmoi.toml.tmpl @@ -1,7 +1,7 @@ [data.git] name = {{ promptStringOnce . "git.name" "git.name" .chezmoi.username | quote }} email = {{ promptStringOnce . "git.email" "git.email" "" | quote }} - signingkey = {{ promptStringOnce . "git.signingkey" "git.signingkey" "" | quote }} + signingKey = {{ promptStringOnce . "git.signingKey" "git.signingKey" "" | quote }} [data.ssh] bitwardenItem = {{ promptStringOnce . "ssh.bitwardenItem" "ssh.bitwardenItem" "" | quote }} diff --git a/.chezmoiignore b/.chezmoiignore index 4b7c06e..34d994e 100644 --- a/.chezmoiignore +++ b/.chezmoiignore @@ -12,6 +12,7 @@ justfile {{- if not (env "BW_SESSION") }} .ssh/config .config/ngrok/ngrok.yml +Library/Application Support/ngrok/ngrok.yml {{- end }} {{- if ne .chezmoi.os "darwin" }} diff --git a/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json b/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json deleted file mode 100644 index 484a7c5..0000000 --- a/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Profiles": [ - { - "Name": "Chezmoi", - "Guid": "6E4B62A6-1C73-4F91-8E32-DAC9C3FE3BBE", - "Dynamic Profile Parent Name": "Default", - "Normal Font": "JetBrainsMonoNFM-Regular 13", - "Non Ascii Font": "JetBrainsMonoNFM-Regular 13", - "Use Non-ASCII Font": false - } - ] -} diff --git a/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json.tmpl b/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json.tmpl new file mode 100644 index 0000000..1c461f6 --- /dev/null +++ b/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json.tmpl @@ -0,0 +1,153 @@ +{ + "Profiles": [ + { + "Name": "Chezmoi", + "Guid": "6E4B62A6-1C73-4F91-8E32-DAC9C3FE3BBE", + "Dynamic Profile Parent Name": "Default", + "Normal Font": "JetBrainsMonoNFM-Regular 13", + "Non Ascii Font": "JetBrainsMonoNFM-Regular 13", + "Use Non-ASCII Font": false, + "Custom Command": "Yes", + "Command": "{{ if eq .chezmoi.arch "arm64" }}/opt/homebrew{{ else }}/usr/local{{ end }}/bin/fish --login", + "Use Separate Colors for Light and Dark Mode": false, + "Background Color": { + "Color Space": "sRGB", + "Red Component": 0.180392, + "Green Component": 0.203922, + "Blue Component": 0.25098 + }, + "Foreground Color": { + "Color Space": "sRGB", + "Red Component": 0.847059, + "Green Component": 0.870588, + "Blue Component": 0.913725 + }, + "Bold Color": { + "Color Space": "sRGB", + "Red Component": 0.847059, + "Green Component": 0.870588, + "Blue Component": 0.913725 + }, + "Cursor Color": { + "Color Space": "sRGB", + "Red Component": 0.847059, + "Green Component": 0.870588, + "Blue Component": 0.913725 + }, + "Cursor Text Color": { + "Color Space": "sRGB", + "Red Component": 0.180392, + "Green Component": 0.203922, + "Blue Component": 0.25098 + }, + "Selection Color": { + "Color Space": "sRGB", + "Red Component": 0.262745, + "Green Component": 0.298039, + "Blue Component": 0.368627 + }, + "Selected Text Color": { + "Color Space": "sRGB", + "Red Component": 0.847059, + "Green Component": 0.870588, + "Blue Component": 0.913725 + }, + "Ansi 0 Color": { + "Color Space": "sRGB", + "Red Component": 0.231373, + "Green Component": 0.258824, + "Blue Component": 0.321569 + }, + "Ansi 1 Color": { + "Color Space": "sRGB", + "Red Component": 0.74902, + "Green Component": 0.380392, + "Blue Component": 0.415686 + }, + "Ansi 2 Color": { + "Color Space": "sRGB", + "Red Component": 0.639216, + "Green Component": 0.745098, + "Blue Component": 0.54902 + }, + "Ansi 3 Color": { + "Color Space": "sRGB", + "Red Component": 0.921569, + "Green Component": 0.796078, + "Blue Component": 0.545098 + }, + "Ansi 4 Color": { + "Color Space": "sRGB", + "Red Component": 0.505882, + "Green Component": 0.631373, + "Blue Component": 0.756863 + }, + "Ansi 5 Color": { + "Color Space": "sRGB", + "Red Component": 0.705882, + "Green Component": 0.556863, + "Blue Component": 0.678431 + }, + "Ansi 6 Color": { + "Color Space": "sRGB", + "Red Component": 0.533333, + "Green Component": 0.752941, + "Blue Component": 0.815686 + }, + "Ansi 7 Color": { + "Color Space": "sRGB", + "Red Component": 0.898039, + "Green Component": 0.913725, + "Blue Component": 0.941176 + }, + "Ansi 8 Color": { + "Color Space": "sRGB", + "Red Component": 0.298039, + "Green Component": 0.337255, + "Blue Component": 0.415686 + }, + "Ansi 9 Color": { + "Color Space": "sRGB", + "Red Component": 0.74902, + "Green Component": 0.380392, + "Blue Component": 0.415686 + }, + "Ansi 10 Color": { + "Color Space": "sRGB", + "Red Component": 0.639216, + "Green Component": 0.745098, + "Blue Component": 0.54902 + }, + "Ansi 11 Color": { + "Color Space": "sRGB", + "Red Component": 0.921569, + "Green Component": 0.796078, + "Blue Component": 0.545098 + }, + "Ansi 12 Color": { + "Color Space": "sRGB", + "Red Component": 0.505882, + "Green Component": 0.631373, + "Blue Component": 0.756863 + }, + "Ansi 13 Color": { + "Color Space": "sRGB", + "Red Component": 0.705882, + "Green Component": 0.556863, + "Blue Component": 0.678431 + }, + "Ansi 14 Color": { + "Color Space": "sRGB", + "Red Component": 0.560784, + "Green Component": 0.737255, + "Blue Component": 0.733333 + }, + "Ansi 15 Color": { + "Color Space": "sRGB", + "Red Component": 0.92549, + "Green Component": 0.937255, + "Blue Component": 0.956863 + } + } + ] +} diff --git a/Library/Application Support/private_ngrok/private_ngrok.yml.tmpl b/Library/Application Support/private_ngrok/private_ngrok.yml.tmpl index 099a849..2c25ea5 100644 --- a/Library/Application Support/private_ngrok/private_ngrok.yml.tmpl +++ b/Library/Application Support/private_ngrok/private_ngrok.yml.tmpl @@ -3,5 +3,5 @@ {{- if $bitwardenItem }} version: "3" agent: - authtoken: {{ output "bw" "get" "notes" $bitwardenItem }} + authtoken: {{ output "bw" "get" "notes" $bitwardenItem "--nointeraction" }} {{- end }} diff --git a/Library/private_LaunchAgents/local.mlx.coder-next.plist.tmpl b/Library/private_LaunchAgents/local.mlx.coder-next.plist.tmpl new file mode 100644 index 0000000..c61cb55 --- /dev/null +++ b/Library/private_LaunchAgents/local.mlx.coder-next.plist.tmpl @@ -0,0 +1,31 @@ + + + + + + Label + local.mlx.coder-next + + ProgramArguments + + {{ .chezmoi.homeDir }}/.local/bin/mlx_lm.server + --model + mlx-community/Qwen3-Coder-Next-4bit + --host + 127.0.0.1 + --port + 8080 + --prefill-step-size + 512 + --max-tokens + 32768 + + + StandardOutPath + /tmp/mlx-coder-next.log + + StandardErrorPath + /tmp/mlx-coder-next-error.log + + diff --git a/Library/private_LaunchAgents/local.mlx.qwen30b.plist.tmpl b/Library/private_LaunchAgents/local.mlx.qwen30b.plist.tmpl new file mode 100644 index 0000000..f4df562 --- /dev/null +++ b/Library/private_LaunchAgents/local.mlx.qwen30b.plist.tmpl @@ -0,0 +1,29 @@ + + + + + + Label + local.mlx.qwen30b + + ProgramArguments + + {{ .chezmoi.homeDir }}/.local/bin/mlx_lm.server + --model + mlx-community/Qwen3-Coder-30B-A3B-Instruct-8bit + --host + 127.0.0.1 + --port + 8080 + --max-tokens + 32768 + + + StandardOutPath + /tmp/mlx-qwen30b.log + + StandardErrorPath + /tmp/mlx-qwen30b-error.log + + diff --git a/README.md b/README.md index 47de726..67f25ea 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,33 @@ chezmoi apply --dry-run --verbose chezmoi apply ``` +Without `BW_SESSION`, chezmoi skips the private SSH and ngrok configs and leaves +existing copies untouched. An invalid session fails without prompting; unlock +Bitwarden again to include secrets in the diff or apply. + +The installer includes Atuin and Zed on both supported platforms, plus Raycast +on macOS. The macOS `Chezmoi` iTerm2 profile launches fish with a Nord dark palette. +See [Setup](wiki/Setup.md) for updating an existing machine. + ## Testing -Prerequisite: [just installed](https://github.com/casey/just). +Install [just](https://github.com/casey/just). Local checks also need Rust/Cargo; +template tests need Python 3 and chezmoi. Arch container tests need Docker running. See [justfile](justfile) for available test commands. Examples: ```bash -just test-arch +just check +just test-templates just test-arch-ci +just test-arch-bootstrap-ci ``` +See [Install Script Testing](wiki/Install-Script-Testing.md) for test scope and +Apple Silicon Docker limitations. + ## Wiki - [Setup](wiki/Setup.md) @@ -44,3 +58,4 @@ just test-arch-ci - [Bitwarden SSH hosts](wiki/Bitwarden-SSH.md) - [Managing dotfiles](wiki/Managing-Dotfiles.md) - [Template verification](wiki/Template-Verification.md) +- [Local AI](wiki/Local-AI.md) diff --git a/dot_config/fish/config.fish.tmpl b/dot_config/fish/config.fish.tmpl index 676baa5..8da57e2 100644 --- a/dot_config/fish/config.fish.tmpl +++ b/dot_config/fish/config.fish.tmpl @@ -18,6 +18,10 @@ fish_add_path "$HOME/.cargo/bin" fish_add_path "$HOME/go/bin" fish_add_path "$HOME/.local/bin" +if test -d "$HOME/.lando/bin" + fish_add_path "$HOME/.lando/bin" +end + if functions -q load_nvm load_nvm > /dev/stderr end @@ -92,3 +96,6 @@ set -gx VISUAL zed if status is-interactive atuin init fish | source end + +alias qwen-heavy-off='launchctl kill SIGTERM gui/(id -u)/local.mlx.coder-next' +alias qwen-light-off='launchctl kill SIGTERM gui/(id -u)/local.mlx.qwen30b' diff --git a/dot_config/fish/functions/qwen-bootstrap.fish b/dot_config/fish/functions/qwen-bootstrap.fish new file mode 100644 index 0000000..0b7281f --- /dev/null +++ b/dot_config/fish/functions/qwen-bootstrap.fish @@ -0,0 +1,6 @@ +function qwen-bootstrap + for name in local.mlx.coder-next local.mlx.qwen30b + launchctl bootout gui/(id -u)/$name + launchctl bootstrap gui/(id -u) "$HOME/Library/LaunchAgents/$name.plist" + end +end diff --git a/dot_config/fish/functions/qwen-heavy.fish b/dot_config/fish/functions/qwen-heavy.fish new file mode 100644 index 0000000..fff6c1f --- /dev/null +++ b/dot_config/fish/functions/qwen-heavy.fish @@ -0,0 +1,4 @@ +function qwen-heavy + launchctl kill SIGTERM gui/(id -u)/local.mlx.qwen30b 2>/dev/null + launchctl kickstart gui/(id -u)/local.mlx.coder-next +end diff --git a/dot_config/fish/functions/qwen-kill-all.fish b/dot_config/fish/functions/qwen-kill-all.fish new file mode 100644 index 0000000..e1909fb --- /dev/null +++ b/dot_config/fish/functions/qwen-kill-all.fish @@ -0,0 +1,4 @@ +function qwen-kill-all + launchctl kill SIGTERM gui/(id -u)/local.mlx.coder-next 2>/dev/null + launchctl kill SIGTERM gui/(id -u)/local.mlx.qwen30b 2>/dev/null +end diff --git a/dot_config/fish/functions/qwen-light.fish b/dot_config/fish/functions/qwen-light.fish new file mode 100644 index 0000000..51ead97 --- /dev/null +++ b/dot_config/fish/functions/qwen-light.fish @@ -0,0 +1,4 @@ +function qwen-light + launchctl kill SIGTERM gui/(id -u)/local.mlx.coder-next 2>/dev/null + launchctl kickstart gui/(id -u)/local.mlx.qwen30b +end diff --git a/dot_config/private_ngrok/private_ngrok.yml.tmpl b/dot_config/private_ngrok/private_ngrok.yml.tmpl index 099a849..2c25ea5 100644 --- a/dot_config/private_ngrok/private_ngrok.yml.tmpl +++ b/dot_config/private_ngrok/private_ngrok.yml.tmpl @@ -3,5 +3,5 @@ {{- if $bitwardenItem }} version: "3" agent: - authtoken: {{ output "bw" "get" "notes" $bitwardenItem }} + authtoken: {{ output "bw" "get" "notes" $bitwardenItem "--nointeraction" }} {{- end }} diff --git a/dot_gitconfig.tmpl b/dot_gitconfig.tmpl index 7dab2c5..6e3dd65 100644 --- a/dot_gitconfig.tmpl +++ b/dot_gitconfig.tmpl @@ -3,8 +3,8 @@ useConfigOnly = true name = {{ .git.name }} email = {{ .git.email }} - {{- if .git.signingkey }} - signingKey = {{ .git.signingkey }} + {{- if .git.signingKey }} + signingKey = {{ .git.signingKey }} {{- end }} [init] @@ -89,5 +89,5 @@ line-numbers = true {{ end }} -[includeIf "gitdir:~/Code/work/n"] +[includeIf "gitdir:~/Code/work/n/**"] path = ~/.config/git/n.inc diff --git a/installer/packages.toml b/installer/packages.toml index de27f5d..acd4bc3 100644 --- a/installer/packages.toml +++ b/installer/packages.toml @@ -3,7 +3,9 @@ pacman = [ "git", "git-lfs", "fish", + "atuin", "neovim", + "zed", "tree-sitter-cli", "base-devel", "wl-clipboard", @@ -59,6 +61,7 @@ formula = [ "git", "git-lfs", "fish", + "atuin", "neovim", "tree-sitter-cli", "ripgrep", @@ -102,6 +105,8 @@ casks = [ "ngrok", "utm", "iterm2", + "zed", + "raycast", ] [cargo] @@ -126,6 +131,7 @@ packages = [ install_uv = true packages = [ { check = "harlequin", package = "harlequin" }, + { check = "mlx-lm", package = "mlx-lm" }, ] [fish] diff --git a/justfile b/justfile index df12122..de2d5f9 100644 --- a/justfile +++ b/justfile @@ -67,6 +67,10 @@ check-sh: check-rust: cd installer && cargo check +# Test secret templates without accessing Bitwarden +test-templates: + python3 scripts/test-bitwarden-templates.py + # Format Rust installer fmt: cd installer && cargo fmt diff --git a/private_dot_ssh/config.tmpl b/private_dot_ssh/config.tmpl index 53996dc..4704164 100644 --- a/private_dot_ssh/config.tmpl +++ b/private_dot_ssh/config.tmpl @@ -8,5 +8,5 @@ Host * {{- $ssh := get . "ssh" | default dict }} {{- $bitwardenItem := get $ssh "bitwardenItem" | default "" }} {{- if $bitwardenItem }} -{{ output "bw" "get" "notes" $bitwardenItem }} +{{ output "bw" "get" "notes" $bitwardenItem "--nointeraction" }} {{- end }} diff --git a/run_onchange_after_reload-qwen.fish.tmpl b/run_onchange_after_reload-qwen.fish.tmpl new file mode 100644 index 0000000..f2a179c --- /dev/null +++ b/run_onchange_after_reload-qwen.fish.tmpl @@ -0,0 +1,3 @@ +#!/usr/bin/env fish + +qwen-bootstrap diff --git a/scripts/test-bitwarden-templates.py b/scripts/test-bitwarden-templates.py new file mode 100644 index 0000000..166bfee --- /dev/null +++ b/scripts/test-bitwarden-templates.py @@ -0,0 +1,114 @@ +"""Exercise real chezmoi diff/apply with an isolated home and fake Bitwarden.""" + +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +REPO = Path(__file__).resolve().parents[1] +TEMPLATES = { + "private_dot_ssh/config.tmpl": ".ssh/config", + "dot_config/private_ngrok/private_ngrok.yml.tmpl": ".config/ngrok/ngrok.yml", + "Library/Application Support/private_ngrok/private_ngrok.yml.tmpl": + "Library/Application Support/ngrok/ngrok.yml", +} + + +class BitwardenTemplatesTest(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name) + self.source = self.root / "source" + self.destination = self.root / "home" + self.destination.mkdir() + for relative in [".chezmoiignore", *TEMPLATES]: + target = self.source / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(REPO / relative, target) + (self.source / "public.txt").write_text("public config\n") + (self.root / "config.toml").write_text("") + self.bin = self.root / "bin" + self.bin.mkdir() + bw = self.bin / "bw" + bw.write_text("""#!/bin/sh +printf 'called\n' >> "$BW_TEST_CALLS" +if [ "$#" -ne 4 ] || [ "$1" != get ] || [ "$2" != notes ] || [ "$4" != --nointeraction ]; then + echo 'Unexpected interactive Bitwarden lookup' >&2 + exit 2 +fi +if [ "$BW_SESSION" != test-unlocked ]; then + echo 'Vault is locked.' >&2 + exit 1 +fi +case "$3" in + ssh-test) printf 'Host private-test\n HostName example.invalid\n' ;; + ngrok-test) printf 'test-ngrok-token' ;; + *) exit 3 ;; +esac +""") + bw.chmod(0o755) + self.calls = self.root / "calls" + + def chezmoi(self, command, platform, session=None): + environment = dict(os.environ) + environment.pop("BW_SESSION", None) + if session is not None: + environment["BW_SESSION"] = session + environment["PATH"] = str(self.bin) + os.pathsep + environment["PATH"] + environment["BW_TEST_CALLS"] = str(self.calls) + return subprocess.run( + ["chezmoi", "--config", str(self.root / "config.toml"), + "--source", str(self.source), "--destination", str(self.destination), + "--cache", str(self.root / "cache"), + "--persistent-state", str(self.root / "state.boltdb"), + "--override-data", json.dumps({ + "chezmoi": {"os": platform}, + "ssh": {"bitwardenItem": "ssh-test"}, + "ngrok": {"bitwardenItem": "ngrok-test"}, + }), "--no-pager", "--no-tty", command], + env=environment, stdin=subprocess.DEVNULL, capture_output=True, + text=True, timeout=15, + ) + + def test_no_session_skips_secrets_and_preserves_existing_files(self): + for relative in TEMPLATES.values(): + target = self.destination / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text("existing private config\n") + for platform in ["darwin", "linux"]: + for session in [None, ""]: + with self.subTest(platform=platform, session=session): + for command in ["diff", "apply"]: + result = self.chezmoi(command, platform, session) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(self.calls.exists()) + for relative in TEMPLATES.values(): + self.assertEqual((self.destination / relative).read_text(), + "existing private config\n") + self.assertEqual((self.destination / "public.txt").read_text(), "public config\n") + + def test_unlocked_session_renders_secrets(self): + for platform in ["darwin", "linux"]: + with self.subTest(platform=platform): + result = self.chezmoi("diff", platform, "test-unlocked") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("Host private-test", result.stdout) + self.assertEqual(result.stdout.count("authtoken: test-ngrok-token"), + 2 if platform == "darwin" else 1) + + def test_invalid_session_fails_without_prompting(self): + for platform in ["darwin", "linux"]: + with self.subTest(platform=platform): + result = self.chezmoi("diff", platform, "test-invalid") + self.assertNotEqual(result.returncode, 0) + self.assertIn("Vault is locked.", result.stderr) + self.assertNotIn("interactive Bitwarden lookup", result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/wiki/Bitwarden-SSH.md b/wiki/Bitwarden-SSH.md index 72f8cc0..0de4f4a 100644 --- a/wiki/Bitwarden-SSH.md +++ b/wiki/Bitwarden-SSH.md @@ -46,7 +46,7 @@ Use the printed UUID as `ssh.bitwardenItem` if name lookup is unreliable. Bitwarden item notes: ```gotemplate -{{ output "bw" "get" "notes" $bitwardenItem }} +{{ output "bw" "get" "notes" $bitwardenItem "--nointeraction" }} ``` Verify rendering without applying: diff --git a/wiki/Install-Script-Testing.md b/wiki/Install-Script-Testing.md index e5eca9f..041e58f 100644 --- a/wiki/Install-Script-Testing.md +++ b/wiki/Install-Script-Testing.md @@ -27,6 +27,24 @@ just check `cargo fmt --check` and `actionlint` separately when touching Rust formatting or workflow YAML. +Run the Bitwarden template regression tests separately: + +```bash +just test-templates +``` + +These require Python 3 and chezmoi. They run real `chezmoi diff` and `apply` +commands against temporary directories with a fake `bw` executable. They check +macOS and Linux behavior for missing, empty, unlocked, and invalid sessions, +including preservation of existing private files when no session is exported. +They do not read the real vault or modify the home directory. + +To validate the installer package schema, run from the repository root: + +```bash +cargo test --locked --manifest-path installer/Cargo.toml +``` + ## Rebuild Binaries Build the local platform binary: @@ -115,6 +133,22 @@ docker build -f Dockerfile.arch-bootstrap-test-ci -t dotfiles-arch-bootstrap-tes docker run --rm -v "$PWD:/work:ro" dotfiles-arch-bootstrap-test-ci ``` +### Apple Silicon Docker + +The Arch image used by these tests requires x86_64. On Apple Silicon, select +that platform for both image builds and container runs: + +```bash +DOCKER_DEFAULT_PLATFORM=linux/amd64 just test-arch-ci +DOCKER_DEFAULT_PLATFORM=linux/amd64 just test-arch-bootstrap-ci +``` + +Local runs on Apple Silicon have failed during image setup with +`error restricting syscalls via seccomp: 22` and +`switching to sandbox user 'alpm' failed`. This happens before the installer +runs. If encountered, run the Arch tests on an x86_64 Linux Docker host; +`just check` and `just test-templates` can still run locally. + ## macOS The macOS installer supports Apple Silicon only. It rejects Intel macOS. diff --git a/wiki/Installed-Tools.md b/wiki/Installed-Tools.md index 882bee2..2afba23 100644 --- a/wiki/Installed-Tools.md +++ b/wiki/Installed-Tools.md @@ -9,6 +9,8 @@ runtimes, diagnostics, and media/document utilities. - `fish`: default interactive shell. - `functions`: list loaded functions. - `funced name`: edit a fish function. +- `atuin`: shell history search, installed on Arch/CachyOS and macOS. + - Initialized by the fish config in interactive sessions. - `zellij`: terminal workspace/session manager. - `zellij`: start a session. - `zellij list-sessions`: show sessions. @@ -18,7 +20,6 @@ runtimes, diagnostics, and media/document utilities. - `z foo`: jump to a frequently used path matching `foo`. - `zi`: interactive jump. - `fzf`: fuzzy finder used directly and by other tools. - - `Ctrl-r`: fuzzy shell history search. - `find . -type f | fzf`: pick a file. - `lsd`: nicer `ls`. - `ls -la`: detailed listing. @@ -38,6 +39,8 @@ runtimes, diagnostics, and media/document utilities. - `nvim .`: open a project. - `:Lazy`: plugin UI. - `:Mason`: language/tool installer UI. +- `zed`: graphical editor, installed on Arch/CachyOS and macOS. + - The fish config sets `EDITOR` and `VISUAL` to `zed`. - `ripgrep` (`rg`): fast text search. - `rg "text"`: search recursively. - `rg -n "text" path`: include line numbers. @@ -48,6 +51,7 @@ runtimes, diagnostics, and media/document utilities. - `just`: command runner for this repo. - `just --list`: show recipes. - `just check`: local checks. + - `just test-templates`: isolated Bitwarden template checks without vault access. - `just test-arch-ci`: run installer test container. - `actionlint`: validate GitHub Actions workflows. - `actionlint .github/workflows/install-script.yml`. @@ -125,7 +129,13 @@ runtimes, diagnostics, and media/document utilities. ## Platform Notes +- Both platforms install Atuin and Zed through their native package lists: + pacman packages on Arch/CachyOS; an Atuin formula and Zed cask on macOS. - Arch/CachyOS also installs `base-devel`, `wl-clipboard`, `xclip`, Linux Docker/Tailscale packages, and the JetBrains Mono Nerd Font package. - macOS installs Homebrew formulae plus optional casks: Docker Desktop, - Tailscale, and JetBrains Mono Nerd Font. Casks are skipped in CI. + Tailscale, JetBrains Mono Nerd Font, ngrok, UTM, iTerm2, Zed, and Raycast. + Raycast is included only on macOS. Set `INSTALL_CASKS=0` to skip casks, as + the macOS CI job does; they are enabled by default. +- The macOS `Chezmoi` iTerm2 profile uses fish as a login shell, JetBrains Mono + Nerd Font, and Nord dark colors matching the Alacritty config. diff --git a/wiki/Local-AI.md b/wiki/Local-AI.md new file mode 100644 index 0000000..ea06ae1 --- /dev/null +++ b/wiki/Local-AI.md @@ -0,0 +1,23 @@ +# Local AI + +## MLX + +MLX is an array framework for efficient and flexible machine learning on Apple silicon. Basically, it allows us to use LLMs more efficiently on Apple silicon. + +You should have a few useful fish functions to get started: + +```bash +qwen-bootstrap # Load changed plist files to launchagent +qwen-light # Start lighter 30B Qwen Coder model +qwen-heavy # Start heavier Next Coder model +qwen-kill-all # Stop all models (they consume a lot of memory) +``` + +To debug problems: + +```bash +curl http://127.0.0.1:8080/v1/models # Show loaded models +launchctl print gui/(id -u)/local.mlx.coder-next # Print info about the coder next, see plist files for the names of the other models +cat /tmp/mlx-coder-next-error.log # See error logs +launchctl kickstart gui/$(id -u)/local.mlx.coder-next # Manualy start a model +``` diff --git a/wiki/Setup.md b/wiki/Setup.md index f3ef5b4..2bd2c09 100644 --- a/wiki/Setup.md +++ b/wiki/Setup.md @@ -37,7 +37,7 @@ Answer the prompts from `.chezmoi.toml.tmpl`: ```text git.name -> git config user.name git.email -> git config user.email -git.signingkey -> optional git config user.signingkey +git.signingKey -> optional git config user.signingKey ssh.bitwardenItem -> Bitwarden item name or ID for private SSH host blocks ngrok.bitwardenItem -> Bitwarden item name or ID for the ngrok authtoken ``` @@ -54,6 +54,41 @@ chezmoi apply --dry-run --verbose chezmoi apply ``` +Without `BW_SESSION`, `chezmoi diff` and `chezmoi apply` skip `.ssh/config`, +`.config/ngrok/ngrok.yml`, and `Library/Application Support/ngrok/ngrok.yml`. +Existing files stay untouched. An invalid session produces a non-interactive +Bitwarden error; unlock again and export the new session before retrying. + +## Update an existing machine + +Package additions in `installer/packages.toml` do not automatically rerun the +run-once installer. From the configured fish shell, run: + +```fish +dotsetup install +``` + +This installs packages from the current list, including Atuin and Zed on both +supported platforms and Raycast on macOS. Package-list changes do not require +rebuilding the committed installer binary. + +Run `chezmoi diff` and `chezmoi apply` to update the managed config files. + +## iTerm2 on macOS + +The managed dynamic profile is named `Chezmoi` and lives at +`~/Library/Application Support/iTerm2/DynamicProfiles/chezmoi.json`. +Its source is the corresponding `.json.tmpl` file in this repository. + +It launches `/opt/homebrew/bin/fish --login` on Apple Silicon, uses JetBrains +Mono Nerd Font at size 13, and applies Nord dark colors matching Alacritty. +The profile keeps the dark palette in both light and dark macOS appearances. +The template also renders `/usr/local/bin/fish --login` on Intel Macs, although +the package installer supports only Apple Silicon macOS. + +The run-once iTerm2 script selects this profile as the default. After applying +profile updates, open a new session with the `Chezmoi` profile to use them. + ## Bootstrap script `run_once_install-packages.sh.tmpl` is a chezmoi script. Chezmoi runs diff --git a/wiki/Template-Verification.md b/wiki/Template-Verification.md index 7f3691b..f509875 100644 --- a/wiki/Template-Verification.md +++ b/wiki/Template-Verification.md @@ -6,7 +6,7 @@ Render with explicit test data: ```bash chezmoi execute-template \ - --override-data '{"git":{"name":"Alice Example","email":"alice@example.com","signingkey":""}}' \ + --override-data '{"git":{"name":"Alice Example","email":"alice@example.com","signingKey":""}}' \ --file dot_gitconfig.tmpl ``` @@ -47,6 +47,15 @@ cargo check ## Full dry run +Without `BW_SESSION`, chezmoi skips the SSH config and both ngrok config +locations, leaving existing files untouched. Unlock Bitwarden and export +`BW_SESSION` to include them in the diff. Secret lookups use `--nointeraction` +so an invalid session fails without trying to prompt from a template (which +can cause Bitwarden's `ERR_USE_AFTER_CLOSE: readline was closed` error). + +Run the isolated Bitwarden template regression checks with `just test-templates`. +These use a fake `bw` command and do not access your vault. + ```bash chezmoi diff chezmoi apply --dry-run --verbose