feat: add deploy-to command and reorganize ssh config + docs
- dotfiles_manager.sh: add 'deploy-to' command to SCP tracked dotfiles
and scripts directly to servers with no Gitea access
- backs up existing remote files to ~/.dotfiles_backup/remote-<host>-<timestamp>/
before overwriting anything
- flags: --scripts-only, --include-ssh, --no-backup, --dry-run
- ssh/config: reorganize into labeled sections, move work hosts to top,
fix global defaults (proper Host * block, remove deprecated Protocol/KeepAlive),
add inline comments on all port forwards
- README.md: full rewrite with directory layout, profiles, shell layering,
env vars, aliases, bootstrap flow, symlink mechanics, SSH key strategy,
two-machine sync workflow, and deploy-to docs
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
# ssh-export GPG-encrypt private keys → dotfiles/.ssh/keys/*.gpg
|
||||
# ssh-import Decrypt GPG-encrypted keys from dotfiles to ~/.ssh/
|
||||
# remote-bootstrap SSH into another machine and run full setup
|
||||
# deploy-to SCP tracked dotfiles + scripts to a server (no git needed)
|
||||
# help Show this message
|
||||
#
|
||||
# File layout inside ~/dotfiles/:
|
||||
@@ -878,6 +879,166 @@ cmd_remote_bootstrap() {
|
||||
info "Log in and verify: ssh $target 'dotfiles status'"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# COMMAND: deploy-to (push dotfiles to a server that can't reach Gitea)
|
||||
# -----------------------------------------------------------------------
|
||||
cmd_deploy_to() {
|
||||
[ $# -ge 1 ] || die "Usage: deploy-to <user@host> [--scripts-only] [--include-ssh] [--no-backup] [--dry-run]"
|
||||
|
||||
local target="$1"; shift
|
||||
local scripts_only=false
|
||||
local skip_ssh=true # default: skip .ssh/ — avoid pushing keys/config to servers
|
||||
local no_backup=false
|
||||
local dry_run=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--scripts-only) scripts_only=true; shift ;;
|
||||
--include-ssh) skip_ssh=false; shift ;;
|
||||
--no-backup) no_backup=true; shift ;;
|
||||
--dry-run) dry_run=true; shift ;;
|
||||
*) die "Unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
bold "=== Deploy dotfiles → $target ==="
|
||||
echo
|
||||
info "Strategy: SCP files directly to their HOME paths (no git required on remote)"
|
||||
$dry_run && warn "DRY RUN — no files will be transferred."
|
||||
echo
|
||||
|
||||
# ---- 1. Check SSH ----
|
||||
info "Testing SSH connectivity..."
|
||||
if ! ssh -o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=accept-new \
|
||||
"$target" "exit 0" 2>/dev/null; then
|
||||
die "Cannot reach $target. Ensure SSH key access is set up:\n ssh-copy-id $target"
|
||||
fi
|
||||
success "SSH connection OK."
|
||||
echo
|
||||
|
||||
local self_dir; self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local deployed=0 skipped=0
|
||||
|
||||
# ---- 2. Back up remote files locally before overwriting ----
|
||||
# Saved to: ~/.dotfiles_backup/remote-<host>-<timestamp>/
|
||||
if ! $no_backup && ! $dry_run; then
|
||||
local remote_host; remote_host="${target##*@}" # strip user@ prefix for dir name
|
||||
local backup_base="$HOME/.dotfiles_backup/remote-${remote_host}-$(date +%Y%m%d_%H%M%S)"
|
||||
info "Backing up existing remote files → $backup_base"
|
||||
|
||||
# Build the list of remote paths to fetch:
|
||||
# - tracked dotfiles from manifest (applying same filters as deploy)
|
||||
# - remote ~/scripts/ if it exists
|
||||
local remote_paths=()
|
||||
if [ -f "$MANIFEST" ] && ! $scripts_only; then
|
||||
while IFS= read -r rel || [ -n "$rel" ]; do
|
||||
[ -z "$rel" ] && continue
|
||||
$skip_ssh && [[ "$rel" == .ssh/* ]] && continue
|
||||
[[ "$rel" == scripts/* ]] && continue
|
||||
remote_paths+=("$rel")
|
||||
done < "$MANIFEST"
|
||||
fi
|
||||
|
||||
# Always attempt to back up remote scripts/
|
||||
remote_paths+=("scripts/dotfiles_manager.sh" "scripts/bootstrap.sh" "scripts/setup_enterprise_ai_bash.sh")
|
||||
|
||||
local backed_up=0
|
||||
for rel in "${remote_paths[@]}"; do
|
||||
# Check if the file actually exists on the remote before fetching
|
||||
if ssh "$target" "[ -f ~/$rel ]" 2>/dev/null; then
|
||||
local local_dest="$backup_base/$rel"
|
||||
mkdir -p "$(dirname "$local_dest")"
|
||||
if scp -q "$target:~/$rel" "$local_dest" 2>/dev/null; then
|
||||
(( backed_up++ )) || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$backed_up" -gt 0 ]]; then
|
||||
success "Backed up $backed_up remote file(s) → $backup_base"
|
||||
else
|
||||
info "No existing remote files found to back up."
|
||||
rmdir "$backup_base" 2>/dev/null || true
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
$no_backup && ! $dry_run && warn "Skipping remote backup (--no-backup set)."
|
||||
|
||||
# ---- 3. Deploy scripts ----
|
||||
info "Deploying scripts → $target:~/scripts/ ..."
|
||||
local script_files=()
|
||||
for f in \
|
||||
"$self_dir/dotfiles_manager.sh" \
|
||||
"$self_dir/setup_enterprise_ai_bash.sh" \
|
||||
"$self_dir/bootstrap.sh"; do
|
||||
[ -f "$f" ] && script_files+=("$f")
|
||||
done
|
||||
|
||||
if [[ ${#script_files[@]} -gt 0 ]]; then
|
||||
if $dry_run; then
|
||||
for f in "${script_files[@]}"; do
|
||||
echo " [dry-run] scp $(basename "$f") → $target:~/scripts/$(basename "$f")"
|
||||
done
|
||||
else
|
||||
ssh "$target" "mkdir -p ~/scripts"
|
||||
scp -q "${script_files[@]}" "$target:~/scripts/"
|
||||
ssh "$target" "chmod +x ~/scripts/dotfiles_manager.sh ~/scripts/setup_enterprise_ai_bash.sh ~/scripts/bootstrap.sh 2>/dev/null; true"
|
||||
(( deployed += ${#script_files[@]} )) || true
|
||||
success "Scripts deployed (${#script_files[@]} files)."
|
||||
fi
|
||||
fi
|
||||
|
||||
$scripts_only && { echo; success "Done (scripts only)."; return; }
|
||||
|
||||
# ---- 4. Deploy tracked dotfiles from manifest ----
|
||||
[ -f "$MANIFEST" ] || { warn "No manifest found — only scripts were deployed."; return; }
|
||||
|
||||
echo
|
||||
info "Deploying tracked dotfiles → $target home directory..."
|
||||
echo
|
||||
|
||||
while IFS= read -r rel || [ -n "$rel" ]; do
|
||||
[ -z "$rel" ] && continue
|
||||
|
||||
# Skip .ssh/ by default — avoids pushing your private keys/known_hosts to servers
|
||||
if $skip_ssh && [[ "$rel" == .ssh/* ]]; then
|
||||
info "Skipping: ~/$rel (use --include-ssh to override)"
|
||||
(( skipped++ )) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
# Scripts are handled above
|
||||
[[ "$rel" == scripts/* ]] && continue
|
||||
|
||||
local src="$DOTFILES_DIR/$rel"
|
||||
if [ ! -e "$src" ]; then
|
||||
warn "Missing in dotfiles, skipping: $rel"
|
||||
(( skipped++ )) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
if $dry_run; then
|
||||
echo " [dry-run] ~/$rel"
|
||||
else
|
||||
# Ensure parent directory exists on remote
|
||||
local parent; parent="$(dirname "$rel")"
|
||||
[[ "$parent" != "." ]] && ssh "$target" "mkdir -p ~/$parent"
|
||||
scp -q "$src" "$target:~/$rel"
|
||||
success "Deployed: ~/$rel"
|
||||
(( deployed++ )) || true
|
||||
fi
|
||||
done < "$MANIFEST"
|
||||
|
||||
echo
|
||||
if $dry_run; then
|
||||
info "Dry run complete. Re-run without --dry-run to transfer files."
|
||||
else
|
||||
bold "Deploy complete: $deployed file(s) deployed, $skipped skipped."
|
||||
info "Files were copied directly (no symlinks). Re-run deploy-to to push updates."
|
||||
$skip_ssh && info "~/.ssh/ was skipped. Use --include-ssh to also deploy ~/.ssh/config."
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# COMMAND: help
|
||||
# -----------------------------------------------------------------------
|
||||
@@ -906,6 +1067,14 @@ ${BOLD}COMMANDS — SSH & Keys${RESET}
|
||||
${BOLD}COMMANDS — Multi-machine${RESET}
|
||||
remote-bootstrap <user@host> [--profile work|personal]
|
||||
Upload scripts and run full setup on a remote machine
|
||||
deploy-to <user@host> [--scripts-only] [--include-ssh] [--no-backup] [--dry-run]
|
||||
SCP tracked dotfiles + scripts directly to a server.
|
||||
Use when the server can't reach the Gitea repo.
|
||||
Backs up existing remote files locally before overwriting.
|
||||
--scripts-only Only push ~/scripts/, skip dotfiles
|
||||
--include-ssh Also deploy ~/.ssh/config (skipped by default)
|
||||
--no-backup Skip the pre-deploy remote backup
|
||||
--dry-run Preview what would be transferred
|
||||
|
||||
${BOLD}QUICK START — this machine (work)${RESET}
|
||||
./dotfiles_manager.sh init
|
||||
@@ -1042,6 +1211,7 @@ main() {
|
||||
ssh-import) cmd_ssh_import "$@" ;;
|
||||
auth) cmd_auth "$@" ;;
|
||||
remote-bootstrap) cmd_remote_bootstrap "$@" ;;
|
||||
deploy-to) cmd_deploy_to "$@" ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
*)
|
||||
error "Unknown command: $cmd"
|
||||
|
||||
Reference in New Issue
Block a user