Skip to content

Instantly share code, notes, and snippets.

@luishdez
Created September 2, 2026 05:01
Show Gist options
  • Select an option

  • Save luishdez/55130ea114a7e391237640cbbbe0d6bd to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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