Created
September 2, 2026 05:01
-
-
Save luishdez/55130ea114a7e391237640cbbbe0d6bd to your computer and use it in GitHub Desktop.
luishdez dotfiles bootstrap — restauración de Mac nuevo sin make (spec 002/011). URL verificada con checksum en restore-quickstart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # bootstrap.sh — restauración completa SIN make (spec 002 + spec 011). | |
| # | |
| # Pensado para un Mac recién formateado: se ejecuta con `sh bootstrap.sh` | |
| # (solo necesita sh + curl; make no está disponible hasta instalar CLT). | |
| # | |
| # Flujo: Xcode CLT → Homebrew → YubiKey (clave residente, opcional) → | |
| # claves SSH (staging) → clonar dotfiles → paquetes (Brewfile) → stows → | |
| # ENVs (staging) → DeepSeek Harness (opcional) → verificación → limpieza | |
| # del staging de secretos. | |
| # | |
| # Secretos (spec 011 / ADR-003): el repo es PRIVADO y con CERO secretos. | |
| # Tus claves SSH y .env se provisionan desde ~/.restore-secrets (cópialas | |
| # desde otro Mac: AirDrop/USB/Telegram, o desde una nota segura de | |
| # Bitwarden) y se borran al terminar si confirmas. | |
| set -eu | |
| REPO_URL_SSH="git@github.com:luishdez/dotfiles.git" | |
| REPO_URL_HTTPS="https://github.com/luishdez/dotfiles.git" | |
| # Estructura de proyectos: volumen "data" (igual que la máquina principal): | |
| # /Volumes/data/projects/<owner>/<repo>. El bootstrap crea /Volumes/data si | |
| # no existe (una vez, pide sudo) — ver paso 2.4. | |
| REPO_DIR_DEFAULT="/Volumes/data/projects/luishdez/dotfiles" | |
| SECRETS_DIR="${HOME}/.restore-secrets" | |
| DSH_REPO="https://github.com/deepseek-ai/deepseek-harness.git" | |
| DSH_DIR_DEFAULT="/Volumes/data/projects/tools/deepseek-harness" | |
| log() { printf '\033[0;34m[bootstrap]\033[0m %s\n' "$1"; } | |
| warn() { printf '\033[1;33m[aviso]\033[0m %s\n' "$1"; } | |
| ok() { printf '\033[0;32m[ok]\033[0m %s\n' "$1"; } | |
| die() { printf '\033[0;31m[error]\033[0m %s\n' "$1" >&2; exit 1; } | |
| # confirm "prompt" default -> 0 sí / 1 no (default: s=si, n=no) | |
| confirm() { | |
| if [ "$2" = "s" ]; then | |
| printf '%s [S/n] ' "$1" | |
| read ans || ans="" | |
| case "$ans" in n|N) return 1 ;; *) return 0 ;; esac | |
| else | |
| printf '%s [s/N] ' "$1" | |
| read ans || ans="" | |
| case "$ans" in s|S|y|Y) return 0 ;; *) return 1 ;; esac | |
| fi | |
| } | |
| [ "$(uname -s)" = "Darwin" ] || die "Solo macOS (Darwin)." | |
| # 1. Xcode Command Line Tools (proporcionan git, make, clang...) | |
| log "1/8 Xcode Command Line Tools" | |
| if xcode-select -p >/dev/null 2>&1; then | |
| ok "CLT ya instaladas ($(xcode-select -p))" | |
| else | |
| warn "Lanzando la instalación de CLT (aparecerá un diálogo)." | |
| xcode-select --install | |
| printf 'Pulsa Enter cuando la instalación haya terminado... ' | |
| read _ || true | |
| xcode-select -p >/dev/null 2>&1 || die "CLT no instaladas — reintenta o instálalas a mano." | |
| ok "CLT instaladas" | |
| fi | |
| # 2. Homebrew | |
| log "2/8 Homebrew" | |
| if command -v brew >/dev/null 2>&1; then | |
| ok "brew ya instalado ($(command -v brew))" | |
| else | |
| warn "Instalando Homebrew (script oficial)..." | |
| brew_url="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh" | |
| /bin/bash -c "$(curl -fsSL "$brew_url")" | |
| command -v brew >/dev/null 2>&1 || die "Homebrew no se instaló." | |
| ok "Homebrew instalado" | |
| fi | |
| # shellenv: Apple Silicon (/opt/homebrew) e Intel (/usr/local) | |
| if [ -f /opt/homebrew/bin/brew ]; then | |
| PATH="/opt/homebrew/bin:$PATH"; export PATH | |
| elif [ -f /usr/local/bin/brew ]; then | |
| PATH="/usr/local/bin:$PATH"; export PATH | |
| fi | |
| command -v brew >/dev/null 2>&1 || die "brew no accesible tras configurar PATH" | |
| # stow no está en ningún Brewfile: se instala aquí explícitamente | |
| command -v stow >/dev/null 2>&1 || { | |
| log "Instalando stow (requerido por make apply)" | |
| brew install stow | |
| } | |
| # 2.4 Estructura de proyectos: asegura el volumen "data" (igual que la | |
| # máquina principal: /Volumes/data/projects/<owner>/<repo>). /Volumes no es | |
| # escribible sin admin, así que si falta, se crea con sudo (una vez). | |
| if [ ! -d /Volumes/data ]; then | |
| warn "No existe /Volumes/data (volumen 'data')." | |
| if confirm "¿Crearlo ahora con sudo (mkdir /Volumes/data + chown)?" s; then | |
| sudo mkdir -p /Volumes/data | |
| sudo chown "$USER:admin" /Volumes/data | |
| ok "/Volumes/data creado" | |
| else | |
| die "Monta o crea el volumen 'data' y vuelve a ejecutar el bootstrap." | |
| fi | |
| fi | |
| # 2.5 YubiKey (opcional): clave SSH residente (spec 008). La clave | |
| # ed25519-sk vive en el hardware; no hay que transportar ningún archivo | |
| # privado. Requiere el OpenSSH de Homebrew (el de Apple no trae proveedor | |
| # FIDO2 — docs/guides/yubikey.md). | |
| if system_profiler SPUSBDataType 2>/dev/null | grep -qi "yubikey"; then | |
| log "2.5/8 YubiKey detectada" | |
| if [ -x /opt/homebrew/bin/ssh-keygen ] || [ -x /usr/local/bin/ssh-keygen ]; then | |
| ok "OpenSSH de Homebrew disponible" | |
| else | |
| log "Instalando OpenSSH de Homebrew (FIDO2 para claves sk)" | |
| brew install openssh | |
| fi | |
| if confirm "¿Recuperar la clave SSH residente de la YubiKey (PIN + toque)?" s; then | |
| ssh-keygen -K || warn "Recuperación fallida — se usará el staging si existe." | |
| [ -f "$HOME/.ssh/id_ed25519_sk" ] \ | |
| && ok "Clave residente recuperada en ~/.ssh/id_ed25519_sk" | |
| fi | |
| else | |
| log "2.5/8 Sin YubiKey — se usará el staging de claves si existe" | |
| fi | |
| # 3. Secretos (I): claves SSH — antes del clone (el repo es privado) | |
| log "3/8 Secretos — claves SSH" | |
| if [ -d "$SECRETS_DIR" ] && [ -f "$SECRETS_DIR/id_ed25519" ]; then | |
| mkdir -p "$HOME/.ssh" | |
| chmod 700 "$HOME/.ssh" | |
| cp "$SECRETS_DIR/id_ed25519" "$HOME/.ssh/id_ed25519" | |
| if [ -f "$SECRETS_DIR/id_ed25519.pub" ]; then | |
| cp "$SECRETS_DIR/id_ed25519.pub" "$HOME/.ssh/id_ed25519.pub" | |
| chmod 644 "$HOME/.ssh/id_ed25519.pub" | |
| fi | |
| chmod 600 "$HOME/.ssh/id_ed25519" | |
| ok "Claves SSH instaladas desde $SECRETS_DIR" | |
| else | |
| if [ -f "$HOME/.ssh/id_ed25519_sk" ]; then | |
| ok "Clave YubiKey recuperada (ssh-keygen -K) — sin staging necesario" | |
| else | |
| warn "Sin claves en $SECRETS_DIR ni YubiKey: el clone irá por HTTPS." | |
| warn "Consejo: copia id_ed25519(.pub) a $SECRETS_DIR y repite." | |
| fi | |
| fi | |
| # 4. Repo de dotfiles | |
| log "4/8 Clonar dotfiles" | |
| if [ -f "./BACKLOG.md" ] && [ -f "./bootstrap.sh" ]; then | |
| REPO_DIR="$(pwd)" | |
| ok "Ya estamos dentro del repo ($REPO_DIR)" | |
| elif [ -d "$REPO_DIR_DEFAULT/.git" ]; then | |
| REPO_DIR="$REPO_DIR_DEFAULT" | |
| ok "Repo ya clonado en $REPO_DIR" | |
| else | |
| REPO_DIR="$REPO_DIR_DEFAULT" | |
| mkdir -p "$(dirname "$REPO_DIR")" | |
| if [ -f "$HOME/.ssh/id_ed25519" ] || [ -f "$HOME/.ssh/id_ed25519_sk" ]; then | |
| git clone "$REPO_URL_SSH" "$REPO_DIR" || die "Clone por SSH falló." | |
| else | |
| warn "Clone por HTTPS (privado): usa un PAT o autentícate al pedirlo." | |
| git clone "$REPO_URL_HTTPS" "$REPO_DIR" || die "Clone por HTTPS falló." | |
| fi | |
| fi | |
| cd "$REPO_DIR" | |
| # 5. Paquetes (Brewfile) — CLT y brew ya están; esto instala el resto | |
| log "5/8 Instalar paquetes (Brewfile)" | |
| bash src/scripts/setup-dependencies.sh | |
| # 6. Stows (dotfiles aplicados) | |
| log "6/8 Aplicar dotfiles (stow)" | |
| bash src/scripts/setup-stow.sh | |
| # 7. Secretos (II): variables de entorno | |
| log "7/8 Secretos — variables de entorno" | |
| if [ -f "$SECRETS_DIR/.env" ]; then | |
| cp "$SECRETS_DIR/.env" "$HOME/.env" | |
| chmod 600 "$HOME/.env" | |
| ok "~/.env instalado desde el staging" | |
| else | |
| warn "No hay $SECRETS_DIR/.env — provisiona tus ENVs (spec 011) a mano." | |
| fi | |
| # 8. DeepSeek Harness (opcional) | |
| log "8/8 DeepSeek Harness (opcional)" | |
| if confirm "¿Instalar DeepSeek Harness (dsh)? (Node 22+ y pnpm)" s; then | |
| if ! command -v node >/dev/null 2>&1; then | |
| die "Node no está — instálalo con 'brew install node' y repite." | |
| fi | |
| DSH_DIR="${DSH_DIR:-$DSH_DIR_DEFAULT}" | |
| if [ ! -d "$DSH_DIR/.git" ]; then | |
| mkdir -p "$(dirname "$DSH_DIR")" | |
| git clone "$DSH_REPO" "$DSH_DIR" | |
| fi | |
| cd "$DSH_DIR" | |
| corepack enable 2>/dev/null || true | |
| command -v pnpm >/dev/null 2>&1 || npm install -g pnpm@11.7.0 | |
| pnpm install | |
| pnpm run build | |
| mkdir -p "$HOME/bin" | |
| cat > "$HOME/bin/dsh" <<EOF | |
| #!/bin/sh | |
| cd "$DSH_DIR" && exec pnpm dsh "\$@" | |
| EOF | |
| chmod +x "$HOME/bin/dsh" | |
| ok "dsh instalado — wrapper en ~/bin/dsh (añade ~/bin a tu PATH si falta)" | |
| cd "$REPO_DIR" | |
| if confirm "¿Arrancar 'dsh web --no-open' ahora?" n; then | |
| "$HOME/bin/dsh" web --no-open & | |
| fi | |
| fi | |
| # Verificación final | |
| log "Verificación final" | |
| bash src/scripts/check-install.sh || true | |
| # Limpieza del staging de secretos | |
| if [ -d "$SECRETS_DIR" ]; then | |
| if confirm "¿Borrar $SECRETS_DIR (con tus secretos)?" s; then | |
| rm -rf "$SECRETS_DIR" | |
| ok "Staging de secretos eliminado." | |
| else | |
| warn "$SECRETS_DIR conservado — bórralo manualmente cuando puedas." | |
| fi | |
| fi | |
| ok "Restauración completada." | |
| printf '\nSiguientes pasos sugeridos:\n' | |
| printf ' - make privacy (spec 018: blocklist + telemetría, sudo)\n' | |
| printf ' - make sync-dsh ACTION=apply (~/.dsh desde el repo, spec 006)\n' | |
| printf ' - make hooks (gitleaks pre-commit, spec 011)\n' | |
| printf ' - make check (verificación completa)\n' | |
| printf ' - docs/procedures/restore-quickstart.md (guía detallada)\n' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment