Compare commits

...

2 Commits

Author SHA1 Message Date
Kenji Morishige
16cbadd016 fix: deploy-to uses scp -r for directories (fixes .bashrc.d upload) 2026-02-23 17:17:15 -06:00
Kenji Morishige
a6296da5df feat: add hosts/ convention for centrally managed server .bashrc.local
- hosts/etqc-kenjim-11.bashrc.local: per-host local config for work server,
  managed from kenjim-mbp and deployed via 'dotfiles deploy-to'.
  Credentials replaced with CHANGEME placeholders — set real values on
  server after first deploy, never commit actual secrets.
- dotfiles_manager.sh: deploy-to step 5 auto-detects hosts/<hostname>.bashrc.local
  and SCPs it to ~/.bashrc.local on the remote (with backup of existing file)
- .gitignore: clarify that hosts/*.bashrc.local is intentionally tracked
  (existing .bashrc.local rule only matches the exact filename)
- README.md: document hosts/ layout, workflow, and credential placeholder strategy
2026-02-23 17:12:29 -06:00
4 changed files with 128 additions and 8 deletions

6
.gitignore vendored
View File

@@ -33,6 +33,10 @@
*.secrets *.secrets
vault/ vault/
# Machine-local overrides — never commit (written by setup_enterprise_ai_bash.sh) # Machine-local overrides at HOME level — never commit (written by setup_enterprise_ai_bash.sh)
.bashrc.local .bashrc.local
.bash_profile.local .bash_profile.local
# Per-host .bashrc.local files ARE committed — managed centrally in dotfiles/hosts/
# Files are named <hostname>.bashrc.local and deployed via: dotfiles deploy-to user@host
# hosts/*.bashrc.local is intentionally tracked (gitignore rules above only match exact name)

View File

@@ -75,6 +75,8 @@ Three scripts drive the system:
│ │ ├── setup_enterprise_ai_bash.sh → symlinked from ~/scripts/setup_enterprise_ai_bash.sh │ │ ├── setup_enterprise_ai_bash.sh → symlinked from ~/scripts/setup_enterprise_ai_bash.sh
│ │ └── bootstrap.sh → symlinked from ~/scripts/bootstrap.sh │ │ └── bootstrap.sh → symlinked from ~/scripts/bootstrap.sh
│ ├── .dotfiles_manifest # internal list of tracked HOME-relative paths │ ├── .dotfiles_manifest # internal list of tracked HOME-relative paths
│ ├── hosts/
│ │ └── <hostname>.bashrc.local # per-server local configs (deployed via deploy-to)
│ ├── install.sh # portable restore script (auto-generated) │ ├── install.sh # portable restore script (auto-generated)
│ └── README.md # this file │ └── README.md # this file
@@ -249,6 +251,34 @@ Files are copied directly (not symlinked). Re-run `deploy-to` any time you
want to push updates. `~/.ssh/` is skipped by default to avoid accidentally want to push updates. `~/.ssh/` is skipped by default to avoid accidentally
pushing private keys or your personal known_hosts to a shared server. pushing private keys or your personal known_hosts to a shared server.
### Centrally managing `~/.bashrc.local` for servers
Work servers can't reach the Gitea repo, so their `~/.bashrc.local` is managed
centrally from `kenjim-mbp` using per-host files in `dotfiles/hosts/`:
```
dotfiles/hosts/
└── <hostname>.bashrc.local # deployed as ~/.bashrc.local on that server
```
`deploy-to` automatically detects and deploys the matching file:
```bash
# Edit the server's local config on kenjim-mbp:
$EDITOR ~/dotfiles/hosts/etqc-kenjim-11.bashrc.local
# Commit and push from kenjim-mbp:
dotfiles push "fix: update etqc-kenjim-11 local config"
# Deploy to the server (no git access needed on the server):
dotfiles deploy-to kenjim@etqc-kenjim-11
```
The `hosts/` files are committed to git. They may contain non-secret
machine-specific variables (`MACHINE_PROFILE`, `MACHINE_HOST`, `AWS_PROFILE`,
etc.). **Do not commit real passwords or tokens** — use `CHANGEME` placeholders
and set real values manually on the server after first deploy.
--- ---
## Dotfiles Management — How Symlinks Work ## Dotfiles Management — How Symlinks Work

View File

@@ -0,0 +1,39 @@
### MACHINE_LOCAL — managed centrally in dotfiles/hosts/ on kenjim-mbp
### Deployed via: dotfiles deploy-to kenjim@etqc-kenjim-11
### DO NOT edit directly on the server — edit in ~/dotfiles/hosts/ and re-deploy
# Host : etqc-kenjim-11
# Profile : work (server)
# =============================================================================
export MACHINE_PROFILE="work"
export MACHINE_HOST="etqc-kenjim-11"
# Prevent shell auto-logout on idle
unset TMOUT
# =============================================================================
# ServiceNow API credentials
# =============================================================================
export SN_USERNAME='_integ-soap-read'
export SN_PASSWORD='CHANGEME' # set real value — never commit
# =============================================================================
# LDAP bind credentials
# Referenced by ldaps() and ldaps2() in ~/.bashrc.d/30_work.sh
# =============================================================================
export JNPR_LDAP_BIND_PW='CHANGEME' # was: xqYzhL%lLe!FIr!67LJX%7a^PWOWY0
export JNPR_LDAP_BIND_PW2='CHANGEME' # was: tF#w3St@nGqq36XZDym#857U)v4xKw
# =============================================================================
# Unified Hub (Artifactory) credentials
# Referenced by unified-hub-login() and unified-hub-engtech-bin-upload()
# in ~/.bashrc.d/30_work.sh
# =============================================================================
export UNIFIED_HUB_USERNAME='kenjim@juniper.net'
export UNIFIED_HUB_TOKEN='CHANGEME' # base64 API token from Artifactory
# =============================================================================
# AWS — server uses named profiles from ~/.aws/config loaded by k8configs env
# =============================================================================
export AWS_PROFILE=pgdb-qnc
export AWS_SDK_LOAD_CONFIG=1

View File

@@ -944,11 +944,17 @@ cmd_deploy_to() {
local backed_up=0 local backed_up=0
for rel in "${remote_paths[@]}"; do for rel in "${remote_paths[@]}"; do
# Check if the file actually exists on the remote before fetching # Check if the file/dir actually exists on the remote before fetching
if ssh "$target" "[ -f ~/$rel ]" 2>/dev/null; then if ssh "$target" "[ -e ~/$rel ]" 2>/dev/null; then
local local_dest="$backup_base/$rel" local local_dest="$backup_base/$rel"
mkdir -p "$(dirname "$local_dest")" mkdir -p "$(dirname "$local_dest")"
if scp -q "$target:~/$rel" "$local_dest" 2>/dev/null; then # Use -r for directories, plain scp for files
if ssh "$target" "[ -d ~/$rel ]" 2>/dev/null; then
mkdir -p "$local_dest"
if scp -rq "$target:~/$rel/." "$local_dest/" 2>/dev/null; then
(( backed_up++ )) || true
fi
elif scp -q "$target:~/$rel" "$local_dest" 2>/dev/null; then
(( backed_up++ )) || true (( backed_up++ )) || true
fi fi
fi fi
@@ -1018,17 +1024,58 @@ cmd_deploy_to() {
fi fi
if $dry_run; then if $dry_run; then
echo " [dry-run] ~/$rel" echo " [dry-run] ~/$rel$([ -d "$src" ] && echo '/')"
else else
# Ensure parent directory exists on remote
local parent; parent="$(dirname "$rel")" local parent; parent="$(dirname "$rel")"
[[ "$parent" != "." ]] && ssh "$target" "mkdir -p ~/$parent" if [ -d "$src" ]; then
scp -q "$src" "$target:~/$rel" # For directories: remove stale dest first (avoids double-nesting on re-run)
# then scp -r into the parent so the dir name is preserved correctly.
ssh "$target" "rm -rf ~/$rel"
if [[ "$parent" == "." ]]; then
scp -rq "$src" "$target:~/"
else
ssh "$target" "mkdir -p ~/$parent"
scp -rq "$src" "$target:~/$parent/"
fi
else
[[ "$parent" != "." ]] && ssh "$target" "mkdir -p ~/$parent"
scp -q "$src" "$target:~/$rel"
fi
success "Deployed: ~/$rel" success "Deployed: ~/$rel"
(( deployed++ )) || true (( deployed++ )) || true
fi fi
done < "$MANIFEST" done < "$MANIFEST"
# ---- 5. Deploy hosts/<hostname>.bashrc.local → ~/.bashrc.local ----
# Strip user@ prefix, then strip domain suffix to get short hostname
local remote_short; remote_short="${target##*@}"
remote_short="${remote_short%%.*}"
local host_local="$DOTFILES_DIR/hosts/${remote_short}.bashrc.local"
if [ -f "$host_local" ]; then
echo
info "Found host-specific config: hosts/${remote_short}.bashrc.local"
if $dry_run; then
echo " [dry-run] hosts/${remote_short}.bashrc.local → ~/.bashrc.local"
else
# Back up existing remote .bashrc.local if not already captured above
if $no_backup; then
: # skip
elif ssh "$target" '[ -f ~/.bashrc.local ]' 2>/dev/null; then
local bl_backup="$HOME/.dotfiles_backup/remote-${remote_short}-$(date +%Y%m%d_%H%M%S)"
mkdir -p "$bl_backup"
scp -q "$target:~/.bashrc.local" "$bl_backup/.bashrc.local" 2>/dev/null || true
info "Backed up remote ~/.bashrc.local → $bl_backup/.bashrc.local"
fi
scp -q "$host_local" "$target:~/.bashrc.local"
success "Deployed: hosts/${remote_short}.bashrc.local → ~/.bashrc.local"
(( deployed++ )) || true
fi
else
info "No host-specific config found at hosts/${remote_short}.bashrc.local — skipping."
info "Create one to manage ~/.bashrc.local centrally: dotfiles/hosts/${remote_short}.bashrc.local"
fi
echo echo
if $dry_run; then if $dry_run; then
info "Dry run complete. Re-run without --dry-run to transfer files." info "Dry run complete. Re-run without --dry-run to transfer files."