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
1 change: 1 addition & 0 deletions docs/specs/shell_auth_startup/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
| AUTH-009 | Tailnet-only self-hosted Atuin and portable lmsh terminal profile | 2026-07-25 | `b44b1eb`, `2cee8ea` |
| AUTH-010 | Deny-by-default lmsh source target boundary | 2026-07-25 | `4fdc25e` |
| AUTH-010-session-credentials | Session-isolated GCP credentials with stale-file rematerialization | 2026-08-01 | `a8e0d9a` |
| AUTH-011-cache-relocation | Routed supported CLI caches and Pulumi home through sentinel-guarded native paths; paused PyCharm, Prefect, and Codex | 2026-08-09 | `712e31e` |
18 changes: 18 additions & 0 deletions docs/specs/shell_auth_startup/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Shell Auth Startup Changelog

## 2026-08-09

- Routed Playwright, uv, pre-commit, npm, and Pulumi through their native path
controls only when the existing external-state sentinel is present. Inherited
managed values clear on the fallback path without overriding unrelated values.
- Copied Playwright browsers and Pulumi home to external storage while retaining
internal rollback copies. Live path checks passed for all CLI tools, including
a Playwright Chromium screenshot, uv package execution, npm cache verification,
pre-commit database initialization, and Pulumi plugin inventory.
- Rolled back the attempted PyCharm properties migration after review found no
sentinel-aware fallback. Prefect and Codex remain internal because their CLIs
are unavailable for migration integrity tests. Host cleanup remains operator
blocked by runtime filesystem policy and interactive `sudo`.
- Validation: 7 focused tests, Bash/Zsh render syntax, exact chezmoi deployment,
Git diff checks, and independent review passed after rollback hardening.
Accepted risk: `AUTH-011-AR1`. Release is not applicable. Final Push remains
pending lifecycle completion. Implementation Gate Commit: `712e31e`.

## 2026-08-01

- Materialized GCP credentials in a private directory per loading shell and
Expand Down
46 changes: 42 additions & 4 deletions docs/specs/shell_auth_startup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
| Maintenance | Pin image and clients, review updates and accepted risks, retain rollback until restore is proven |
| Authorities | Chezmoi rendering, shell syntax, pytest contracts, Compose validation, health/sync probes, lifecycle audit, and independent review |

Current cycle `AUTH-010-session-credentials` is elevated-risk sensitive-data
handling work. The security module applies. Release, Deploy, Operate, and
Maintain/Retire are not applicable because this cycle changes only local
chezmoi source; Review/Integrate is required.
Current cycle `AUTH-011-cache-relocation` is elevated-risk local storage and
sensitive-data handling work. The security module applies. Release is not
applicable; Deploy, Operate, Maintain/Retire, and Review/Integrate are required.

## Domain

Expand All @@ -34,6 +33,11 @@ Entities:
- `ClockifyPoller`: SketchyBar plugin that checks current Clockify timer.
- `TerminalProfile`: portable Bash, Atuin, zoxide, and Starship configuration
selected by chezmoi machine intent and operating system.
- `ExternalCacheRoot`: mounted Mac mini cache storage below `/Volumes/ext/state/cache`.
- `NativeCacheClient`: Playwright, uv, pre-commit, or npm configured
through its supported cache-path interface.
- `PulumiHome`: Pulumi's credential, workspace, schema, and plugin directory,
selected through its supported `PULUMI_HOME` interface.
- `TerminalTargetAllowlist`: deny-by-default set of files and scripts that the
personal source may apply to an `lmsh` guest.
- `AtuinClient`: one machine-local history database, record store, encryption
Expand Down Expand Up @@ -219,6 +223,21 @@ Glossary:

### Feature: Private self-hosted history sync

### Feature: Native external caches

**Scenario: External cache volume is available**
- Given the Mac mini state sentinel exists
- When an interactive shell starts
- Then Playwright, uv, pre-commit, and npm receive native paths below
`ExternalCacheRoot`
- And Pulumi uses `/Volumes/ext/state/pulumi` as `PulumiHome`

**Scenario: External cache volume is unavailable**
- Given the Mac mini state sentinel is absent
- When an interactive shell starts
- Then no external CLI cache variable is exported
- And each CLI retains its native local default

**Scenario: Tailnet client reaches Atuin**
- Given the pinned Atuin container is healthy on Mac loopback
- When an authorized Mac or Lima client connects to the stable tailnet URL
Expand Down Expand Up @@ -311,6 +330,15 @@ Glossary:
- **Invariant:** optional shell tools are command-guarded.
- **Invariant:** the lmsh target excludes SSH private keys, GitHub credentials,
1Password integration, GUI applications, and macOS service configuration.
- **Invariant:** external CLI cache exports are Mac-mini-only and require the
existing `/Volumes/ext/state/.dotfiles-ai-state` sentinel.
- **Invariant:** Playwright, uv, pre-commit, and npm use only their
documented native path controls; shell-wide `XDG_CACHE_HOME` and cache
symlinks are not used.
- **Invariant:** Pulumi uses its documented `PULUMI_HOME`; Prefect databases and
Codex session state remain internal until their installed runtimes can pass
migration integrity tests. PyCharm remains internal until its external paths
can fail back when the volume is unavailable.
- **Invariant:** `TerminalTargetAllowlist` denies all targets by default and
re-includes only `.bash_profile`, `.bashrc`, `.common_profile`,
`.config/atuin/config.toml`, `.config/starship.toml`, and
Expand Down Expand Up @@ -343,6 +371,13 @@ Glossary:
- **Post:** remote failure does not prevent local history search or capture.

### Accepted Risk
- `AUTH-011-AR1`: the operator approved moving Pulumi credentials and executable
caches to the existing unencrypted, `noowners` external state volume after the
trust limitation was reported. The state sentinel, retained internal rollback
copies, and trusted physical custody compensate but do not provide encryption
or offline tamper resistance. Owner: operator. Review before the volume leaves
trusted custody, another local account gains access, or the storage policy
changes.
- `AUTH-009-AR1`: the operator approved synchronizing MGM shell history through
the same personal account, making work commands and paths decryptable on
personal clients. End-to-end encryption, secret filters, tailnet-only ingress,
Expand All @@ -352,6 +387,9 @@ Glossary:
## Verification

- Shell syntax checks pass for edited scripts.
- Rendered Mac mini shell startup selects external CLI caches only with the
sentinel present; absent-sentinel startup retains local defaults.
- PyCharm custom properties remain absent after the attempted migration rollback.
- Static search confirms no Herdr profile auto-`secret` block remains.
- Static search confirms Clockify poller has no `op read` call.
- Static search confirms Databricks config has no `onepasswordRead` call.
Expand Down
18 changes: 18 additions & 0 deletions dot_common_profile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ alias ch="clickhouse client --host \"\$(op read 'op://Personal/ClickHouse Cloud/

# --- Environment Variables ---

{{ if eq .machine_type "mac-mini" -}}
if [ -f "/Volumes/ext/state/.dotfiles-ai-state" ]; then
export DOTFILES_CACHE_ROOT="/Volumes/ext/state/cache"
export PLAYWRIGHT_BROWSERS_PATH="$DOTFILES_CACHE_ROOT/playwright"
export UV_CACHE_DIR="$DOTFILES_CACHE_ROOT/uv"
export PRE_COMMIT_HOME="$DOTFILES_CACHE_ROOT/pre-commit"
export npm_config_cache="$DOTFILES_CACHE_ROOT/npm"
export PULUMI_HOME="/Volumes/ext/state/pulumi"
else
[ "${PLAYWRIGHT_BROWSERS_PATH:-}" = "/Volumes/ext/state/cache/playwright" ] && unset PLAYWRIGHT_BROWSERS_PATH
[ "${UV_CACHE_DIR:-}" = "/Volumes/ext/state/cache/uv" ] && unset UV_CACHE_DIR
[ "${PRE_COMMIT_HOME:-}" = "/Volumes/ext/state/cache/pre-commit" ] && unset PRE_COMMIT_HOME
[ "${npm_config_cache:-}" = "/Volumes/ext/state/cache/npm" ] && unset npm_config_cache
[ "${PULUMI_HOME:-}" = "/Volumes/ext/state/pulumi" ] && unset PULUMI_HOME
[ "${DOTFILES_CACHE_ROOT:-}" = "/Volumes/ext/state/cache" ] && unset DOTFILES_CACHE_ROOT
fi
{{ end -}}

# Carapace completion bridges
export CARAPACE_BRIDGES="zsh,fish,bash,inshellisense"

Expand Down
63 changes: 63 additions & 0 deletions tests/test_terminal_environment.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os
import subprocess
from pathlib import Path


Expand Down Expand Up @@ -28,6 +30,67 @@ def test_lmsh_profile_is_portable_and_excludes_credentials():
assert "/opt/homebrew" in macos


def test_mac_mini_uses_native_external_cache_paths():
profile = text("dot_common_profile.tmpl")
assert '[ -f "/Volumes/ext/state/.dotfiles-ai-state" ]' in profile
for setting in (
'PLAYWRIGHT_BROWSERS_PATH="$DOTFILES_CACHE_ROOT/playwright"',
'UV_CACHE_DIR="$DOTFILES_CACHE_ROOT/uv"',
'PRE_COMMIT_HOME="$DOTFILES_CACHE_ROOT/pre-commit"',
'npm_config_cache="$DOTFILES_CACHE_ROOT/npm"',
'PULUMI_HOME="/Volumes/ext/state/pulumi"',
):
assert setting in profile
for variable in (
"DOTFILES_CACHE_ROOT",
"PLAYWRIGHT_BROWSERS_PATH",
"UV_CACHE_DIR",
"PRE_COMMIT_HOME",
"npm_config_cache",
"PULUMI_HOME",
):
assert f'unset {variable}' in profile


def cache_env_script(sentinel):
profile = text("dot_common_profile.tmpl")
return profile.split('{{ if eq .machine_type "mac-mini" -}}', 1)[1].split("{{ end -}}", 1)[0].replace(
"/Volumes/ext/state/.dotfiles-ai-state", str(sentinel)
)


def test_external_cache_exports_and_fallback(tmp_path):
sentinel = tmp_path / "sentinel"
script = cache_env_script(sentinel)
managed = {
"DOTFILES_CACHE_ROOT": "/Volumes/ext/state/cache",
"PLAYWRIGHT_BROWSERS_PATH": "/Volumes/ext/state/cache/playwright",
"UV_CACHE_DIR": "/Volumes/ext/state/cache/uv",
"PRE_COMMIT_HOME": "/Volumes/ext/state/cache/pre-commit",
"npm_config_cache": "/Volumes/ext/state/cache/npm",
"PULUMI_HOME": "/Volumes/ext/state/pulumi",
}

shells = [shell for shell in ("/bin/bash", "/bin/zsh") if Path(shell).exists()]
assert shells
for shell in shells:
sentinel.touch()
exported = subprocess.run(
[shell, "-c", script + "\nenv"], check=True, capture_output=True, text=True
).stdout
for name, value in managed.items():
assert f"{name}={value}" in exported

sentinel.unlink()
inherited = os.environ | managed | {"UV_CACHE_DIR": "/custom/uv"}
fallback = subprocess.run(
[shell, "-c", script + "\nenv"], env=inherited, check=True, capture_output=True, text=True
).stdout
assert "UV_CACHE_DIR=/custom/uv" in fallback
for name in managed.keys() - {"UV_CACHE_DIR"}:
assert f"{name}=" not in fallback


def test_lmsh_targets_are_deny_by_default():
ignored = text(".chezmoiignore")
lmsh = ignored.split('{{ if eq .machine_type "lmsh" }}', 1)[1].split("{{ end }}", 1)[0]
Expand Down
Loading