diff --git a/src/dotfiles-sync/README.md b/src/dotfiles-sync/README.md index ecb9972..0f7d617 100644 --- a/src/dotfiles-sync/README.md +++ b/src/dotfiles-sync/README.md @@ -1,6 +1,6 @@ # Dotfiles Sync (dotfiles-sync) -Syncs local Git, SSH, GPG, npm, gh, cargo, pip, yarn/pnpm config files into the devcontainer. Optionally syncs cloud credentials (AWS, kube, Docker, gh OAuth token) — opt-in only. Works on macOS, Linux, Windows (WSL and native), GitHub Codespaces, Gitpod, and DevPod. Uses a **merge strategy** for established files and a **copy-if-absent** strategy for new ones — never overwrites existing values, safe alongside cloud platform native auth and GPG signing. +Syncs local Git, SSH, GPG, npm, and yarn config files into the devcontainer. Optionally syncs cloud credentials (AWS, kube, Docker) — opt-in only. Works on macOS, Linux, Windows (WSL and native), GitHub Codespaces, Gitpod, and DevPod. Uses a **merge strategy** for established files and a **copy-if-absent** strategy for new ones — never overwrites existing values, safe alongside cloud platform native auth and GPG signing. ## Usage @@ -33,7 +33,6 @@ That's it. The feature auto-detects the environment and adapts its behavior. | Option | Type | Default | Description | |--------|------|---------|-------------| | `username` | string | `node` | Container username that receives synchronized config files | -| `syncGhAuth` | boolean | `false` | Copy `~/.config/gh/hosts.yml` (GitHub OAuth token used by `gh` CLI) into the container's `$HOME`. Skipped on cloud environments (Codespaces / Gitpod / DevPod inject their own token). When `false`, the file is bind-mounted into `/mnt/h4dotfiles` (Feature `mounts` cannot be conditional) but **never copied** to `$HOME` and never read by anything else. Prefer the [`github-dev`](../github-dev/) feature with `GH_TOKEN` for fine-grained PATs. | | `syncAwsConfig` | boolean | `false` | Sync `~/.aws/config` (profiles only — `~/.aws/credentials` is **never** synced). | | `syncKubeConfig` | boolean | `false` | Sync `~/.kube/config` (cluster credentials and tokens). Skipped on cloud environments. | | `syncDockerConfig` | boolean | `false` | Sync `~/.docker/config.json` (registry auth tokens). Skipped on cloud environments. | @@ -45,7 +44,6 @@ That's it. The feature auto-detects the environment and adapts its behavior. | Local Path | Final Target | Strategy | Purpose | |------------|--------------|----------|---------| | `~/.gitconfig` | `~/.gitconfig` | Merge via `git config` | Git user configuration | -| `~/.gitignore_global` | `~/.gitignore_global` | Copy-if-absent | Personal global gitignore | | `~/.config/git/ignore` | `~/.config/git/ignore` | Copy-if-absent | XDG global gitignore | | `~/.config/git/attributes` | `~/.config/git/attributes` | Copy-if-absent | XDG global gitattributes | | `~/.config/git/config-*` | `~/.config/git/config-*` | Copy-if-absent | Modular git includes | @@ -53,16 +51,16 @@ That's it. The feature auto-detects the environment and adapts its behavior. | `~/.gnupg` | `~/.gnupg` | Copy-if-absent (skipped on cloud) | GPG keys for commit signing | | `~/.npmrc` | `~/.npmrc` | Merge line-by-line | npm registry auth | | `~/.yarnrc.yml` | `~/.yarnrc.yml` | Copy-if-absent | yarn registries / settings | -| `~/.config/pnpm/rc` | `~/.config/pnpm/rc` | Copy-if-absent | pnpm settings | -| `~/.config/gh/config.yml` | `~/.config/gh/config.yml` | Copy-if-absent | gh CLI preferences (no token) | -| `~/.cargo/config.toml` | `~/.cargo/config.toml` | Copy-if-absent | Cargo registries / profiles | -| `~/.config/pip/pip.conf` | `~/.config/pip/pip.conf` | Copy-if-absent | pip index URLs | ### Opt-in (sensitive) +**Operational note — bind-mounts are unconditional:** DevContainer Feature `mounts` cannot be gated on option values. The files below are always bind-mounted into `/mnt/h4dotfiles` at container start, regardless of the option value. The option only controls whether `sync-files.sh` copies the staged file into `$HOME`. Consequences: + +- **Startup failure risk** — Docker file bind-mounts fail hard if the source path does not exist on the host. If you don't have `~/.aws/config`, `~/.kube/config`, or `~/.docker/config.json`, the container will fail to start even if the corresponding option is `false`. Create the file (it can be empty) to unblock startup. +- **Data visible in staging** — even when the option is `false`, the host file is accessible inside the container at `/mnt/h4dotfiles/`. Nothing reads that path unless the option is enabled, but if your threat model requires full isolation, do not use the feature for that credential. + | Local Path | Option | Notes | |------------|--------|-------| -| `~/.config/gh/hosts.yml` | `syncGhAuth` | GitHub OAuth token used by `gh`. The file is bind-mounted into `/mnt/h4dotfiles` unconditionally (Feature `mounts` cannot be gated on options) but **only copied to `$HOME` when `syncGhAuth: true`**. Skipped on cloud environments. For fine-grained PATs, prefer [`github-dev`](../github-dev/) + `GH_TOKEN`. | | `~/.aws/config` | `syncAwsConfig` | AWS profiles. `~/.aws/credentials` (long-lived access keys) is **not bind-mounted** and never synced. | | `~/.kube/config` | `syncKubeConfig` | Kubernetes cluster credentials. Skipped on cloud environments. | | `~/.docker/config.json` | `syncDockerConfig` | Docker registry auth tokens. Skipped on cloud environments. | @@ -74,7 +72,7 @@ That's it. The feature auto-detects the environment and adapts its behavior. ## GitHub authentication -`gh` CLI authentication is **off by default**. Pick whichever fits your workflow: +`gh` CLI authentication is not managed by this feature. Pick whichever fits your workflow: 1. **`github-dev` feature + `GH_TOKEN`** (recommended for fine-grained scope): @@ -90,23 +88,7 @@ That's it. The feature auto-detects the environment and adapts its behavior. } ``` -2. **Sync your local `gh auth login` token** (`syncGhAuth: true`): - - ```jsonc - { - "features": { - "ghcr.io/helpers4/devcontainer/dotfiles-sync:1": { - "syncGhAuth": true - } - } - } - ``` - - The token is copied with `chmod 600` and only if `~/.config/gh/hosts.yml` does not already exist in the container. Skipped on Codespaces / Gitpod / DevPod (the platform injects its own token). - - **Security note** — because DevContainer Feature `mounts` cannot be conditional on options, `~/.config/gh/hosts.yml` is bind-mounted into `/mnt/h4dotfiles/.config/gh/hosts.yml` whether or not you opt in. Nothing reads that path unless `syncGhAuth: true`, but if your threat model considers any in-container exposure unacceptable, use approach 1 or 3 instead. - -3. **`gh auth login` inside the container** — token stays in the container only. +2. **`gh auth login` inside the container** — token stays in the container only. ## Merge Strategy @@ -118,7 +100,7 @@ That's it. The feature auto-detects the environment and adapts its behavior. | `.ssh/known_hosts` | Appends host entries not already present | | `.ssh` keys | Copies files only if destination does not exist | | `.gnupg` | Copied on local/WSL; **skipped on cloud environments** (see below) | -| All other files (gitignore_global, gh/config.yml, cargo, pip, yarn, pnpm, …) | **Copy-if-absent** — never overwrites an existing target | +| All other files (git/ignore, git/attributes, yarnrc.yml, …) | **Copy-if-absent** — never overwrites an existing target | ### Cloud environment protection @@ -253,6 +235,7 @@ ssh-add -l ## Version History +- **v1.0.4**: Removed bind-mounts for files that are frequently absent on host machines and have little value inside a devcontainer: `~/.gitignore_global` (redundant with `~/.config/git/` directory mount), `~/.config/pnpm/rc` (pnpm store-dir is counter-productive in a container), `~/.config/gh/config.yml` and `~/.config/gh/hosts.yml` (gh CLI auth managed separately), `~/.cargo/config.toml` (cargo not relevant in most containers), `~/.config/pip/pip.conf` (too environment-specific). Docker file bind-mounts fail hard if the source path doesn't exist on the host, which was causing containers to fail to start. The `syncGhAuth` option is removed. - **v1.0.3**: Fixed incompatibility with `docker-in-docker` feature — staging directory moved from `/tmp/dotfiles-sync/` to `/mnt/h4dotfiles/` to avoid being hidden by the tmpfs that `docker-in-docker` mounts on `/tmp` at container start. - **v1.0.2**: Added `syncGhAuth` opt-in to copy `~/.config/gh/hosts.yml` (GitHub OAuth token used by `gh` CLI) into `$HOME`. Default `false`, skipped on cloud environments. The file is bind-mounted into `/tmp/dotfiles-sync/` regardless (Feature `mounts` cannot be conditional) but only copied to `$HOME` when the option is enabled. For fine-grained PATs prefer the `github-dev` feature with `GH_TOKEN`. - **v1.0.1**: Stop bind-mounting the `~/.config/gh` directory. Only `~/.config/gh/config.yml` (CLI preferences) is mounted. Added 3 opt-in booleans for sensitive files: `syncAwsConfig`, `syncKubeConfig`, `syncDockerConfig` — all default `false` and skipped on cloud environments. Added low-risk dotfiles (gitignore_global, git/ignore, git/attributes, yarnrc.yml, pnpm/rc, cargo/config.toml, pip/pip.conf) with copy-if-absent strategy. `~/.aws/credentials` is never bind-mounted. diff --git a/src/dotfiles-sync/devcontainer-feature.json b/src/dotfiles-sync/devcontainer-feature.json index 8eaf6a5..66ad382 100644 --- a/src/dotfiles-sync/devcontainer-feature.json +++ b/src/dotfiles-sync/devcontainer-feature.json @@ -1,8 +1,8 @@ { "id": "dotfiles-sync", - "version": "1.0.3", + "version": "1.0.4", "name": "Dotfiles Sync", - "description": "Syncs local Git, SSH, GPG, npm, gh CLI prefs, cargo, pip, pnpm/yarn config files into the devcontainer. Optionally syncs cloud credentials (AWS, kube, Docker, gh OAuth token) — opt-in only. Works on macOS, Linux, Windows (WSL), Codespaces, Gitpod, DevPod. Merges instead of overwriting.", + "description": "Syncs local Git, SSH, GPG, npm, yarn config files into the devcontainer. Optionally syncs cloud credentials (AWS, kube, Docker) — opt-in only. Works on macOS, Linux, Windows (WSL), Codespaces, Gitpod, DevPod. Merges instead of overwriting.", "documentationURL": "https://github.com/helpers4/devcontainer/tree/main/src/dotfiles-sync", "options": { "username": { @@ -10,11 +10,6 @@ "default": "node", "description": "Username in the container that should receive synchronized local config files" }, - "syncGhAuth": { - "type": "boolean", - "default": false, - "description": "Sync ~/.config/gh/hosts.yml (GitHub OAuth token used by gh CLI). Skipped on cloud environments (Codespaces/Gitpod/DevPod inject their own token). Prefer the github-dev feature with GH_TOKEN for fine-grained PATs." - }, "syncAwsConfig": { "type": "boolean", "default": false, @@ -37,11 +32,6 @@ "target": "/mnt/h4dotfiles/.gitconfig", "type": "bind" }, - { - "source": "${localEnv:HOME}/.gitignore_global", - "target": "/mnt/h4dotfiles/.gitignore_global", - "type": "bind" - }, { "source": "${localEnv:HOME}/.config/git", "target": "/mnt/h4dotfiles/.config/git", @@ -67,31 +57,6 @@ "target": "/mnt/h4dotfiles/.yarnrc.yml", "type": "bind" }, - { - "source": "${localEnv:HOME}/.config/pnpm/rc", - "target": "/mnt/h4dotfiles/.config/pnpm/rc", - "type": "bind" - }, - { - "source": "${localEnv:HOME}/.config/gh/config.yml", - "target": "/mnt/h4dotfiles/.config/gh/config.yml", - "type": "bind" - }, - { - "source": "${localEnv:HOME}/.config/gh/hosts.yml", - "target": "/mnt/h4dotfiles/.config/gh/hosts.yml", - "type": "bind" - }, - { - "source": "${localEnv:HOME}/.cargo/config.toml", - "target": "/mnt/h4dotfiles/.cargo/config.toml", - "type": "bind" - }, - { - "source": "${localEnv:HOME}/.config/pip/pip.conf", - "target": "/mnt/h4dotfiles/.config/pip/pip.conf", - "type": "bind" - }, { "source": "${localEnv:HOME}/.aws/config", "target": "/mnt/h4dotfiles/.aws/config", diff --git a/src/dotfiles-sync/install.sh b/src/dotfiles-sync/install.sh index 5823493..b07a6f4 100755 --- a/src/dotfiles-sync/install.sh +++ b/src/dotfiles-sync/install.sh @@ -11,7 +11,6 @@ set -e USERNAME="${_BUILD_ARG_USERNAME:-"${USERNAME:-"node"}"}" -SYNC_GH_AUTH="${_BUILD_ARG_DOTFILES_SYNC_SYNCGHAUTH:-"${SYNCGHAUTH:-"false"}"}" SYNC_AWS_CONFIG="${_BUILD_ARG_DOTFILES_SYNC_SYNCAWSCONFIG:-"${SYNCAWSCONFIG:-"false"}"}" SYNC_KUBE_CONFIG="${_BUILD_ARG_DOTFILES_SYNC_SYNCKUBECONFIG:-"${SYNCKUBECONFIG:-"false"}"}" SYNC_DOCKER_CONFIG="${_BUILD_ARG_DOTFILES_SYNC_SYNCDOCKERCONFIG:-"${SYNCDOCKERCONFIG:-"false"}"}" @@ -28,7 +27,6 @@ echo "Setting up dotfiles-sync devcontainer feature..." echo " Container user: ${USERNAME}" echo " Home directory: ${TARGET_HOME}" echo " Mount staging: ${SOURCE_HOME}" -echo " Sync gh auth: ${SYNC_GH_AUTH}" echo " Sync AWS config: ${SYNC_AWS_CONFIG}" echo " Sync kube config: ${SYNC_KUBE_CONFIG}" echo " Sync Docker config: ${SYNC_DOCKER_CONFIG}" @@ -42,10 +40,6 @@ mkdir -p \ "${TARGET_HOME}/.ssh" \ "${TARGET_HOME}/.gnupg" \ "${TARGET_HOME}/.config/git" \ - "${TARGET_HOME}/.config/gh" \ - "${TARGET_HOME}/.config/pip" \ - "${TARGET_HOME}/.config/pnpm" \ - "${TARGET_HOME}/.cargo" \ "${TARGET_HOME}/.aws" \ "${TARGET_HOME}/.kube" \ "${TARGET_HOME}/.docker" 2>/dev/null || true @@ -57,7 +51,6 @@ if getent passwd "${USERNAME}" >/dev/null 2>&1; then "${TARGET_HOME}/.ssh" \ "${TARGET_HOME}/.gnupg" \ "${TARGET_HOME}/.config" \ - "${TARGET_HOME}/.cargo" \ "${TARGET_HOME}/.aws" \ "${TARGET_HOME}/.kube" \ "${TARGET_HOME}/.docker" \ @@ -77,7 +70,6 @@ cat > /usr/local/share/dotfiles-sync/config << CONF_EOF DOTFILES_SYNC_USERNAME="${USERNAME}" DOTFILES_SYNC_SOURCE="${SOURCE_HOME}" DOTFILES_SYNC_TARGET="${TARGET_HOME}" -DOTFILES_SYNC_GH_AUTH="${SYNC_GH_AUTH}" DOTFILES_SYNC_AWS_CONFIG="${SYNC_AWS_CONFIG}" DOTFILES_SYNC_KUBE_CONFIG="${SYNC_KUBE_CONFIG}" DOTFILES_SYNC_DOCKER_CONFIG="${SYNC_DOCKER_CONFIG}" @@ -152,16 +144,11 @@ echo " Shell start -> SSH_AUTH_SOCK detection + sync fallback" echo "" echo "Targets:" echo " Git config -> ${TARGET_HOME}/.gitconfig" -echo " Git ignore/attrs -> ${TARGET_HOME}/.gitignore_global, ${TARGET_HOME}/.config/git/" +echo " Git ignore/attrs -> ${TARGET_HOME}/.config/git/" echo " SSH keys -> ${TARGET_HOME}/.ssh/" echo " GPG keys -> ${TARGET_HOME}/.gnupg/" echo " npm tokens -> ${TARGET_HOME}/.npmrc" echo " yarn config -> ${TARGET_HOME}/.yarnrc.yml" -echo " pnpm config -> ${TARGET_HOME}/.config/pnpm/rc" -echo " gh CLI prefs -> ${TARGET_HOME}/.config/gh/config.yml" -echo " gh OAuth token -> ${TARGET_HOME}/.config/gh/hosts.yml [opt-in: ${SYNC_GH_AUTH}]" -echo " cargo config -> ${TARGET_HOME}/.cargo/config.toml" -echo " pip config -> ${TARGET_HOME}/.config/pip/pip.conf" echo " AWS profiles -> ${TARGET_HOME}/.aws/config [opt-in: ${SYNC_AWS_CONFIG}]" echo " kube config -> ${TARGET_HOME}/.kube/config [opt-in: ${SYNC_KUBE_CONFIG}]" echo " Docker auth -> ${TARGET_HOME}/.docker/config.json [opt-in: ${SYNC_DOCKER_CONFIG}]" diff --git a/src/dotfiles-sync/sync-files.sh b/src/dotfiles-sync/sync-files.sh index 6b4bb22..b059dab 100755 --- a/src/dotfiles-sync/sync-files.sh +++ b/src/dotfiles-sync/sync-files.sh @@ -19,19 +19,11 @@ # .gnupg -> skipped on cloud environments (GPG handled natively there). # known_hosts -> merge line-by-line (append missing host entries). # ── extra files (v1.0.1+) — copy-if-absent strategy: -# .gitignore_global, .config/git/{ignore,attributes,config-*} -# .yarnrc.yml, .config/pnpm/rc, .cargo/config.toml, .config/pip/pip.conf -# .config/gh/config.yml -> copy-if-absent (CLI preferences only) -# .config/gh/hosts.yml -> opt-in (DOTFILES_SYNC_GH_AUTH), skipped on cloud env +# .config/git/{ignore,attributes,config-*}, .yarnrc.yml # .aws/config -> opt-in (DOTFILES_SYNC_AWS_CONFIG) # .kube/config -> opt-in (DOTFILES_SYNC_KUBE_CONFIG) # .docker/config.json -> opt-in (DOTFILES_SYNC_DOCKER_CONFIG) # -# NOTE: ~/.config/gh/hosts.yml (GitHub OAuth token) is NOT synced by default. -# Enable `syncGhAuth: true` to opt in (skipped on Codespaces/Gitpod/DevPod -# which inject their own token). For fine-grained PATs, prefer the github-dev -# feature with GH_TOKEN, or run `gh auth login` once in the container. - # No set -e: sync as much as possible even if one part fails. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -48,7 +40,6 @@ fi USERNAME="${DOTFILES_SYNC_USERNAME}" SOURCE_HOME="${DOTFILES_SYNC_SOURCE}" TARGET_HOME="${DOTFILES_SYNC_TARGET}" -SYNC_GH_AUTH="${DOTFILES_SYNC_GH_AUTH:-false}" SYNC_AWS_CONFIG="${DOTFILES_SYNC_AWS_CONFIG:-false}" SYNC_KUBE_CONFIG="${DOTFILES_SYNC_KUBE_CONFIG:-false}" SYNC_DOCKER_CONFIG="${DOTFILES_SYNC_DOCKER_CONFIG:-false}" @@ -332,8 +323,6 @@ _copy_if_absent() { fi } -# ── Sync .gitignore_global ──────────────────────────────────────────────────── -_copy_if_absent ".gitignore_global" ".gitignore_global" "644" # ── Sync ~/.config/git/{ignore,attributes,config-*} ─────────────────────────── if [ -d "${SOURCE_HOME}/.config/git" ]; then @@ -353,35 +342,6 @@ fi # ── Sync ~/.yarnrc.yml ──────────────────────────────────────────────────────── _copy_if_absent ".yarnrc.yml" ".yarnrc.yml" "644" -# ── Sync ~/.config/pnpm/rc ──────────────────────────────────────────────────── -_copy_if_absent ".config/pnpm/rc" ".config/pnpm/rc" "644" - -# ── Sync ~/.cargo/config.toml ───────────────────────────────────────────────── -_copy_if_absent ".cargo/config.toml" ".cargo/config.toml" "644" - -# ── Sync ~/.config/pip/pip.conf ─────────────────────────────────────────────── -_copy_if_absent ".config/pip/pip.conf" ".config/pip/pip.conf" "644" - -# ── Sync ~/.config/gh/config.yml (CLI preferences only) ─────────────────── -if [ -e "${SOURCE_HOME}/.config/gh/config.yml" ]; then - mkdir -p "${TARGET_HOME}/.config/gh" - _copy_if_absent ".config/gh/config.yml" ".config/gh/config.yml" "600" -else - echo " .config/gh/config.yml: not found in staging" -fi - -# ── Sync ~/.config/gh/hosts.yml (opt-in: GitHub OAuth token) ─────────────── -if [ "${SYNC_GH_AUTH}" = "true" ]; then - if [ "${IS_CLOUD_ENV}" = "true" ]; then - echo " .config/gh/hosts.yml: skipped (cloud env — platform manages GitHub auth)" - else - mkdir -p "${TARGET_HOME}/.config/gh" - _copy_if_absent ".config/gh/hosts.yml" ".config/gh/hosts.yml [GitHub OAuth token]" "600" - fi -else - echo " .config/gh/hosts.yml: skipped (opt-in: set 'syncGhAuth' to enable)" -fi - # ── Sync ~/.aws/config (opt-in) ─────────────────────────────────────────────── if [ "${SYNC_AWS_CONFIG}" = "true" ]; then mkdir -p "${TARGET_HOME}/.aws" @@ -422,10 +382,8 @@ if [ "$(id -u)" -eq 0 ] && getent passwd "${USERNAME}" >/dev/null 2>&1; then "${TARGET_HOME}/.gnupg" \ "${TARGET_HOME}/.gitconfig" \ "${TARGET_HOME}/.npmrc" \ - "${TARGET_HOME}/.gitignore_global" \ "${TARGET_HOME}/.yarnrc.yml" \ "${TARGET_HOME}/.config" \ - "${TARGET_HOME}/.cargo" \ "${TARGET_HOME}/.aws" \ "${TARGET_HOME}/.kube" \ "${TARGET_HOME}/.docker" 2>/dev/null || true diff --git a/test/dotfiles-sync/test.sh b/test/dotfiles-sync/test.sh index 96264da..2f03ace 100755 --- a/test/dotfiles-sync/test.sh +++ b/test/dotfiles-sync/test.sh @@ -79,7 +79,7 @@ if [ -f "$CONFIG_FILE" ]; then exit 1 fi # Test 5b: Opt-in flags persisted in config - for flag in DOTFILES_SYNC_GH_AUTH DOTFILES_SYNC_AWS_CONFIG DOTFILES_SYNC_KUBE_CONFIG DOTFILES_SYNC_DOCKER_CONFIG; do + for flag in DOTFILES_SYNC_AWS_CONFIG DOTFILES_SYNC_KUBE_CONFIG DOTFILES_SYNC_DOCKER_CONFIG; do if grep -q "^${flag}=" "$CONFIG_FILE"; then echo " PASS: ${flag} present in config" else @@ -92,30 +92,8 @@ else exit 1 fi -# Test 5d: hosts.yml is staged in /mnt/h4dotfiles (always — the mount is -# unconditional because Feature `mounts` cannot be gated on options). The -# security boundary is the *copy* step in sync-files.sh, which only writes to -# $HOME/.config/gh/hosts.yml when DOTFILES_SYNC_GH_AUTH=true and the env is -# not a cloud platform. The staging path is internal to the feature and never -# read otherwise. -GH_STAGE="/mnt/h4dotfiles/.config/gh" -if [ -d "${GH_STAGE}" ] && [ ! -L "${GH_STAGE}" ]; then - for f in "${GH_STAGE}"/*; do - [ -e "$f" ] || continue - name=$(basename "$f") - case "$name" in - config.yml|hosts.yml) ;; - *) - echo "FAIL: unexpected mount in ${GH_STAGE}: ${name}" - exit 1 - ;; - esac - done -fi -echo "PASS: only config.yml and hosts.yml may be staged under /mnt/h4dotfiles/.config/gh" - # Test 5e: opt-in directories created at build time -for dir in ".aws" ".kube" ".docker" ".cargo" ".config/pip" ".config/pnpm" ".config/gh" ".config/git"; do +for dir in ".aws" ".kube" ".docker" ".config/git"; do if [ -d "${TARGET_HOME}/${dir}" ]; then echo "PASS: ${dir} directory exists" else