From 9e37d15b14f248f6e3cf2b37e2c189d4f7c184e8 Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:11:14 -0500 Subject: [PATCH 1/3] Use ~/.dotfiles as chezmoi source --- .chezmoi.toml.tmpl | 1 + README.md | 3 +++ bootstrap.cmd | 2 +- bootstrap.sh | 2 +- tests/linux/assert-state.sh | 3 +++ tests/static/validate-architecture.py | 1 + tests/windows/assert-allsigned.ps1 | 5 +++++ tests/windows/assert-state.ps1 | 6 ++++++ 8 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .chezmoi.toml.tmpl mode change 100644 => 100755 bootstrap.sh mode change 100644 => 100755 tests/linux/assert-state.sh mode change 100644 => 100755 tests/static/validate-architecture.py diff --git a/.chezmoi.toml.tmpl b/.chezmoi.toml.tmpl new file mode 100644 index 0000000..8e02426 --- /dev/null +++ b/.chezmoi.toml.tmpl @@ -0,0 +1 @@ +sourceDir = {{ .chezmoi.sourceDir | quote }} diff --git a/README.md b/README.md index 2acff13..2a166fa 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This repository uses [chezmoi](https://www.chezmoi.io/) to deploy regular dotfiles on Windows and Linux. Repository support files stay outside the `home/` source state. +Fresh bootstrap installations keep the repository at `~/.dotfiles`. The generated chezmoi configuration persists that source directory, so regular commands such as `chezmoi update`, `chezmoi source-path`, and `chezmoi cd` use `~/.dotfiles` without extra flags. Existing installations are not moved automatically. + ## Quick installation ### Windows @@ -42,6 +44,7 @@ For dotfiles plus installer-managed package/application/module updates, use `upd ## State details +- `.chezmoi.toml.tmpl` persists the source directory selected during `chezmoi init`; fresh bootstraps explicitly select `~/.dotfiles`. - `home/.chezmoiroot` is represented by the repository-level `.chezmoiroot`, pointing to `home`. - Windows-only `.wslconfig` and Linux-only Zsh/Sheldon state are filtered by `home/.chezmoiignore`. - `~/.gitconfig.local`, `~/.codex/config.toml`, and `~/.codex/rules/default.rules` use chezmoi create-only semantics and are not overwritten after creation. diff --git a/bootstrap.cmd b/bootstrap.cmd index 861aeca..7163445 100644 --- a/bootstrap.cmd +++ b/bootstrap.cmd @@ -121,7 +121,7 @@ for /f "delims=" %%R in ('chezmoi.exe source-path 2^>nul') do set "SOURCE_ROOT=% if defined SOURCE_ROOT if exist "%SOURCE_ROOT%\.chezmoiroot" goto existing_chezmoi if defined SOURCE_ROOT for %%P in ("%SOURCE_ROOT%\..") do if exist "%%~fP\.chezmoiroot" goto existing_chezmoi -chezmoi.exe init "%REPO_URL%" +chezmoi.exe --source "%USERPROFILE%\.dotfiles" init "%REPO_URL%" exit /b %ERRORLEVEL% :existing_chezmoi diff --git a/bootstrap.sh b/bootstrap.sh old mode 100644 new mode 100755 index d57e69b..8d4b579 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -82,7 +82,7 @@ else if [[ -f "$SOURCE_ROOT/.chezmoiroot" || -f "$(dirname "$SOURCE_ROOT")/.chezmoiroot" ]]; then existing_source=true else - chezmoi init "$REPO_URL" + chezmoi --source "$HOME/.dotfiles" init "$REPO_URL" fi fi diff --git a/tests/linux/assert-state.sh b/tests/linux/assert-state.sh old mode 100644 new mode 100755 index c4e0c67..4c2a11a --- a/tests/linux/assert-state.sh +++ b/tests/linux/assert-state.sh @@ -14,6 +14,9 @@ printf 'HOME: %s\n' "$HOME" printf 'chezmoi: %s\n' "$(chezmoi --version)" printf 'chezmoi source-path: %s\n' "$(chezmoi source-path)" +source_path="$(chezmoi source-path)" +[[ "$source_path" == "$HOME/.dotfiles" ]] || fail "chezmoi source path is $source_path, expected $HOME/.dotfiles" + case "$expected_distro" in debian) [[ -f /etc/debian_version ]] || fail "Debian detection marker is missing" ;; arch) [[ -f /etc/arch-release ]] || fail "Arch detection marker is missing" ;; diff --git a/tests/static/validate-architecture.py b/tests/static/validate-architecture.py old mode 100644 new mode 100755 index 19da9f6..2dee9f0 --- a/tests/static/validate-architecture.py +++ b/tests/static/validate-architecture.py @@ -33,6 +33,7 @@ def read_text(path: str) -> str: fail(".chezmoiroot must contain 'home'") required_paths = ( + ".chezmoi.toml.tmpl", "home", "scripts/linux/packages-debian.txt", "scripts/linux/cleanup-broken-managed-links.sh", diff --git a/tests/windows/assert-allsigned.ps1 b/tests/windows/assert-allsigned.ps1 index 274c408..437ffd6 100644 --- a/tests/windows/assert-allsigned.ps1 +++ b/tests/windows/assert-allsigned.ps1 @@ -211,6 +211,11 @@ function Assert-CleanChezMoi { $sourcePath = ((Invoke-Chezmoi @("source-path")) -join "").Trim() Write-Host "chezmoi source-path: $sourcePath" +$expectedSourcePath = [IO.Path]::GetFullPath((Join-Path $HOME ".dotfiles")).TrimEnd([IO.Path]::DirectorySeparatorChar) +$actualSourcePath = [IO.Path]::GetFullPath($sourcePath).TrimEnd([IO.Path]::DirectorySeparatorChar) +if (-not $actualSourcePath.Equals($expectedSourcePath, [StringComparison]::OrdinalIgnoreCase)) { + Fail "chezmoi source path is $actualSourcePath, expected $expectedSourcePath" +} $managedAppsPath = Join-Path $RepoRoot "scripts\windows\managed-apps.csv" $coreApps = @(Import-Csv -LiteralPath $managedAppsPath | Where-Object Category -eq "core") foreach ($app in $coreApps) { diff --git a/tests/windows/assert-state.ps1 b/tests/windows/assert-state.ps1 index f68d1e9..736f5a7 100644 --- a/tests/windows/assert-state.ps1 +++ b/tests/windows/assert-state.ps1 @@ -41,6 +41,12 @@ $sourcePath = ((Invoke-Chezmoi @("source-path")) -join "").Trim() Write-Host "chezmoi source-path: $sourcePath" if (-not (Test-Path -LiteralPath $sourcePath)) { Fail "chezmoi source path does not exist: $sourcePath" } +$expectedSourcePath = [IO.Path]::GetFullPath((Join-Path $HOME ".dotfiles")).TrimEnd([IO.Path]::DirectorySeparatorChar) +$actualSourcePath = [IO.Path]::GetFullPath($sourcePath).TrimEnd([IO.Path]::DirectorySeparatorChar) +if (-not $actualSourcePath.Equals($expectedSourcePath, [StringComparison]::OrdinalIgnoreCase)) { + Fail "chezmoi source path is $actualSourcePath, expected $expectedSourcePath" +} + $requiredFiles = @( (Join-Path $HOME ".gitconfig"), (Join-Path $HOME ".gitconfig.local"), From c5d652ff46d51b3e5fbbf9e58b2d3ca9a6305edb Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:13:40 -0500 Subject: [PATCH 2/3] Place chezmoi config template in source state --- README.md | 2 +- .chezmoi.toml.tmpl => home/.chezmoi.toml.tmpl | 0 tests/static/validate-architecture.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename .chezmoi.toml.tmpl => home/.chezmoi.toml.tmpl (100%) diff --git a/README.md b/README.md index 2a166fa..1bad3b7 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ For dotfiles plus installer-managed package/application/module updates, use `upd ## State details -- `.chezmoi.toml.tmpl` persists the source directory selected during `chezmoi init`; fresh bootstraps explicitly select `~/.dotfiles`. +- `home/.chezmoi.toml.tmpl` persists the source directory selected during `chezmoi init`; fresh bootstraps explicitly select `~/.dotfiles`. - `home/.chezmoiroot` is represented by the repository-level `.chezmoiroot`, pointing to `home`. - Windows-only `.wslconfig` and Linux-only Zsh/Sheldon state are filtered by `home/.chezmoiignore`. - `~/.gitconfig.local`, `~/.codex/config.toml`, and `~/.codex/rules/default.rules` use chezmoi create-only semantics and are not overwritten after creation. diff --git a/.chezmoi.toml.tmpl b/home/.chezmoi.toml.tmpl similarity index 100% rename from .chezmoi.toml.tmpl rename to home/.chezmoi.toml.tmpl diff --git a/tests/static/validate-architecture.py b/tests/static/validate-architecture.py index 2dee9f0..31dd57b 100755 --- a/tests/static/validate-architecture.py +++ b/tests/static/validate-architecture.py @@ -33,7 +33,7 @@ def read_text(path: str) -> str: fail(".chezmoiroot must contain 'home'") required_paths = ( - ".chezmoi.toml.tmpl", + "home/.chezmoi.toml.tmpl", "home", "scripts/linux/packages-debian.txt", "scripts/linux/cleanup-broken-managed-links.sh", From 9dc4418b05fb59a24107e20dfe569769b88ce924 Mon Sep 17 00:00:00 2001 From: Julio Rios <31230801+jsilverdev@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:17:00 -0500 Subject: [PATCH 3/3] Validate dotfiles working tree and source state --- README.md | 2 +- home/.chezmoi.toml.tmpl | 2 +- tests/linux/assert-state.sh | 4 +++- tests/windows/assert-allsigned.ps1 | 8 +++++++- tests/windows/assert-state.ps1 | 8 +++++++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1bad3b7..31f31b5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository uses [chezmoi](https://www.chezmoi.io/) to deploy regular dotfiles on Windows and Linux. Repository support files stay outside the `home/` source state. -Fresh bootstrap installations keep the repository at `~/.dotfiles`. The generated chezmoi configuration persists that source directory, so regular commands such as `chezmoi update`, `chezmoi source-path`, and `chezmoi cd` use `~/.dotfiles` without extra flags. Existing installations are not moved automatically. +Fresh bootstrap installations keep the Git working tree at `~/.dotfiles`. The generated chezmoi configuration persists that directory, so regular commands such as `chezmoi update` and `chezmoi cd` use it without extra flags. Because `.chezmoiroot` points to `home`, `chezmoi source-path` resolves to `~/.dotfiles/home`. Existing installations are not moved automatically. ## Quick installation diff --git a/home/.chezmoi.toml.tmpl b/home/.chezmoi.toml.tmpl index 8e02426..edbecd8 100644 --- a/home/.chezmoi.toml.tmpl +++ b/home/.chezmoi.toml.tmpl @@ -1 +1 @@ -sourceDir = {{ .chezmoi.sourceDir | quote }} +sourceDir = {{ joinPath .chezmoi.homeDir ".dotfiles" | quote }} diff --git a/tests/linux/assert-state.sh b/tests/linux/assert-state.sh index 4c2a11a..c4bfa04 100755 --- a/tests/linux/assert-state.sh +++ b/tests/linux/assert-state.sh @@ -15,7 +15,9 @@ printf 'chezmoi: %s\n' "$(chezmoi --version)" printf 'chezmoi source-path: %s\n' "$(chezmoi source-path)" source_path="$(chezmoi source-path)" -[[ "$source_path" == "$HOME/.dotfiles" ]] || fail "chezmoi source path is $source_path, expected $HOME/.dotfiles" +working_tree="$(chezmoi execute-template '{{ .chezmoi.workingTree }}')" +[[ "$working_tree" == "$HOME/.dotfiles" ]] || fail "chezmoi working tree is $working_tree, expected $HOME/.dotfiles" +[[ "$source_path" == "$HOME/.dotfiles/home" ]] || fail "chezmoi source path is $source_path, expected $HOME/.dotfiles/home" case "$expected_distro" in debian) [[ -f /etc/debian_version ]] || fail "Debian detection marker is missing" ;; diff --git a/tests/windows/assert-allsigned.ps1 b/tests/windows/assert-allsigned.ps1 index 437ffd6..60052ac 100644 --- a/tests/windows/assert-allsigned.ps1 +++ b/tests/windows/assert-allsigned.ps1 @@ -211,7 +211,13 @@ function Assert-CleanChezMoi { $sourcePath = ((Invoke-Chezmoi @("source-path")) -join "").Trim() Write-Host "chezmoi source-path: $sourcePath" -$expectedSourcePath = [IO.Path]::GetFullPath((Join-Path $HOME ".dotfiles")).TrimEnd([IO.Path]::DirectorySeparatorChar) +$expectedWorkingTree = [IO.Path]::GetFullPath((Join-Path $HOME ".dotfiles")).TrimEnd([IO.Path]::DirectorySeparatorChar) +$workingTree = ((Invoke-Chezmoi @("execute-template", "{{ .chezmoi.workingTree }}")) -join "").Trim() +$actualWorkingTree = [IO.Path]::GetFullPath($workingTree).TrimEnd([IO.Path]::DirectorySeparatorChar) +if (-not $actualWorkingTree.Equals($expectedWorkingTree, [StringComparison]::OrdinalIgnoreCase)) { + Fail "chezmoi working tree is $actualWorkingTree, expected $expectedWorkingTree" +} +$expectedSourcePath = [IO.Path]::GetFullPath((Join-Path $expectedWorkingTree "home")).TrimEnd([IO.Path]::DirectorySeparatorChar) $actualSourcePath = [IO.Path]::GetFullPath($sourcePath).TrimEnd([IO.Path]::DirectorySeparatorChar) if (-not $actualSourcePath.Equals($expectedSourcePath, [StringComparison]::OrdinalIgnoreCase)) { Fail "chezmoi source path is $actualSourcePath, expected $expectedSourcePath" diff --git a/tests/windows/assert-state.ps1 b/tests/windows/assert-state.ps1 index 736f5a7..e3813aa 100644 --- a/tests/windows/assert-state.ps1 +++ b/tests/windows/assert-state.ps1 @@ -41,7 +41,13 @@ $sourcePath = ((Invoke-Chezmoi @("source-path")) -join "").Trim() Write-Host "chezmoi source-path: $sourcePath" if (-not (Test-Path -LiteralPath $sourcePath)) { Fail "chezmoi source path does not exist: $sourcePath" } -$expectedSourcePath = [IO.Path]::GetFullPath((Join-Path $HOME ".dotfiles")).TrimEnd([IO.Path]::DirectorySeparatorChar) +$expectedWorkingTree = [IO.Path]::GetFullPath((Join-Path $HOME ".dotfiles")).TrimEnd([IO.Path]::DirectorySeparatorChar) +$workingTree = ((Invoke-Chezmoi @("execute-template", "{{ .chezmoi.workingTree }}")) -join "").Trim() +$actualWorkingTree = [IO.Path]::GetFullPath($workingTree).TrimEnd([IO.Path]::DirectorySeparatorChar) +if (-not $actualWorkingTree.Equals($expectedWorkingTree, [StringComparison]::OrdinalIgnoreCase)) { + Fail "chezmoi working tree is $actualWorkingTree, expected $expectedWorkingTree" +} +$expectedSourcePath = [IO.Path]::GetFullPath((Join-Path $expectedWorkingTree "home")).TrimEnd([IO.Path]::DirectorySeparatorChar) $actualSourcePath = [IO.Path]::GetFullPath($sourcePath).TrimEnd([IO.Path]::DirectorySeparatorChar) if (-not $actualSourcePath.Equals($expectedSourcePath, [StringComparison]::OrdinalIgnoreCase)) { Fail "chezmoi source path is $actualSourcePath, expected $expectedSourcePath"