211 lines
7.1 KiB
Bash
Executable File
211 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
readonly SCRIPT_NAME="$(basename "$0")"
|
|
readonly DEFAULT_OUTPUT_DIR="/var/backups"
|
|
|
|
include_secrets=false
|
|
output_dir="$DEFAULT_OUTPUT_DIR"
|
|
|
|
show_help() {
|
|
cat <<EOF
|
|
Uso: sudo $SCRIPT_NAME [opciones]
|
|
|
|
Crea un respaldo comprimido de la configuración de Ubuntu para replicarla en
|
|
otra instalación. No incluye modelos Ollama, entornos virtuales ni bases de datos.
|
|
|
|
Opciones:
|
|
--output-dir DIR Directorio donde guardar el archivo .tar.gz.
|
|
--include-secrets Incluye claves SSH, certificados privados y secretos
|
|
de OpenClaw. El archivo resultante debe protegerse.
|
|
-h, --help Muestra esta ayuda.
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--output-dir)
|
|
[[ $# -ge 2 ]] || { echo "Falta DIR para --output-dir" >&2; exit 2; }
|
|
output_dir="$2"
|
|
shift 2
|
|
;;
|
|
--include-secrets)
|
|
include_secrets=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Opción desconocida: $1" >&2
|
|
show_help >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "${EUID}" -ne 0 ]]; then
|
|
echo "Ejecuta este script con sudo o como root." >&2
|
|
exit 1
|
|
fi
|
|
|
|
command -v tar >/dev/null || { echo "tar es obligatorio." >&2; exit 1; }
|
|
command -v gzip >/dev/null || { echo "gzip es obligatorio." >&2; exit 1; }
|
|
|
|
hostname_value="$(hostname -s 2>/dev/null || echo unknown)"
|
|
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
backup_name="ubuntu-config-${hostname_value}-${timestamp}"
|
|
work_dir="$(mktemp -d)"
|
|
archive_path="${output_dir}/${backup_name}.tar.gz"
|
|
|
|
cleanup() {
|
|
rm -rf "$work_dir"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
mkdir -p "$work_dir/metadata" "$work_dir/files" "$work_dir/packages" "$work_dir/services"
|
|
mkdir -p "$output_dir"
|
|
umask 077
|
|
|
|
copy_path() {
|
|
local source="$1"
|
|
local destination="$work_dir/files$source"
|
|
|
|
if [[ -e "$source" || -L "$source" ]]; then
|
|
mkdir -p "$(dirname "$destination")"
|
|
cp -a "$source" "$destination"
|
|
fi
|
|
}
|
|
|
|
capture() {
|
|
local name="$1"
|
|
shift
|
|
"$@" > "$work_dir/metadata/$name.txt" 2>&1 || true
|
|
}
|
|
|
|
capture_shell() {
|
|
local name="$1"
|
|
local command_text="$2"
|
|
bash -c "$command_text" > "$work_dir/metadata/$name.txt" 2>&1 || true
|
|
}
|
|
|
|
printf 'Creando respaldo en %s\n' "$archive_path"
|
|
|
|
printf '%s\n' "hostname=$hostname_value" "created_at=$(date --iso-8601=seconds)" \
|
|
"include_secrets=$include_secrets" > "$work_dir/metadata/backup-info.txt"
|
|
|
|
capture_shell os-release 'cat /etc/os-release'
|
|
capture_shell kernel 'uname -a'
|
|
capture_shell hardware 'lscpu; echo; free -h; echo; lsblk -f'
|
|
capture_shell disks 'df -hT; echo; findmnt'
|
|
capture_shell network 'ip -brief address; echo; ip route; echo; resolvectl status 2>/dev/null || true'
|
|
capture_shell users 'getent passwd | awk -F: '\''$3 >= 1000 || $1 == "root" {print $1 ":" $3 ":" $4 ":" $6}'\'''
|
|
capture_shell mounts 'mount'
|
|
# Registrar nombres, no valores que puedan contener credenciales.
|
|
capture_shell environment 'printenv | cut -d= -f1 | sort -u'
|
|
capture_shell nvidia 'command -v nvidia-smi && nvidia-smi -q || true'
|
|
capture_shell ollama 'command -v ollama && ollama list || true'
|
|
capture_shell versions 'command -v node && node --version || true; command -v npm && npm --version || true; command -v python3 && python3 --version || true; command -v ollama && ollama --version || true; command -v openclaw && openclaw --version || true'
|
|
capture_shell ufw 'command -v ufw && ufw status verbose || true'
|
|
capture_shell iptables 'command -v iptables-save && iptables-save || true'
|
|
capture_shell sysctl 'sysctl -a 2>/dev/null'
|
|
capture_shell timers 'systemctl list-timers --all --no-pager'
|
|
capture_shell enabled-services 'systemctl list-unit-files --state=enabled --no-legend --no-pager'
|
|
capture_shell failed-services 'systemctl --failed --no-pager'
|
|
|
|
if command -v dpkg-query >/dev/null; then
|
|
dpkg-query -W -f='${binary:Package}\t${Version}\n' > "$work_dir/packages/dpkg-status.tsv" || true
|
|
fi
|
|
if command -v apt-mark >/dev/null; then
|
|
apt-mark showmanual | sort > "$work_dir/packages/apt-manual.txt" || true
|
|
fi
|
|
if command -v snap >/dev/null; then
|
|
snap list > "$work_dir/packages/snap-list.txt" 2>&1 || true
|
|
fi
|
|
if command -v pip3 >/dev/null; then
|
|
pip3 freeze > "$work_dir/packages/pip3-freeze.txt" 2>&1 || true
|
|
fi
|
|
|
|
systemctl list-unit-files --type=service --no-legend --no-pager \
|
|
> "$work_dir/services/all-service-units.txt" 2>&1 || true
|
|
systemctl list-unit-files --state=enabled --type=service --no-legend --no-pager \
|
|
| awk '{print $1}' | sort -u > "$work_dir/services/enabled-service-names.txt" || true
|
|
|
|
# Configuración del sistema y de los servicios usados por este proyecto.
|
|
for path in \
|
|
/etc/apt \
|
|
/etc/default \
|
|
/etc/environment \
|
|
/etc/fstab \
|
|
/etc/hostname \
|
|
/etc/hosts \
|
|
/etc/issue \
|
|
/etc/netplan \
|
|
/etc/NetworkManager \
|
|
/etc/systemd/system \
|
|
/etc/sysctl.d \
|
|
/etc/modprobe.d \
|
|
/etc/ufw \
|
|
/etc/ollama \
|
|
/etc/nvidia \
|
|
/etc/profile.d
|
|
do
|
|
copy_path "$path"
|
|
done
|
|
|
|
# Configuraciones de usuario que no suelen contener credenciales.
|
|
while IFS=: read -r username _ uid _ _ home _; do
|
|
[[ -d "$home" ]] || continue
|
|
[[ "$uid" -ge 1000 || "$username" == root ]] || continue
|
|
for relative_path in .bashrc .profile .config/systemd/user litellm_config.yaml; do
|
|
copy_path "$home/$relative_path"
|
|
done
|
|
printf '%s\t%s\t%s\t%s\n' "$username" "$uid" "$(id -g "$username" 2>/dev/null || echo 0)" "$home" \
|
|
>> "$work_dir/metadata/users.tsv"
|
|
done < <(getent passwd)
|
|
|
|
if [[ "$include_secrets" == true ]]; then
|
|
printf 'Incluyendo archivos sensibles: SSH, certificados y configuración privada de OpenClaw.\n'
|
|
for path in /etc/ssh /etc/ssl/private /etc/letsencrypt; do
|
|
copy_path "$path"
|
|
done
|
|
while IFS=: read -r username _ uid _ _ home _; do
|
|
[[ -d "$home" ]] || continue
|
|
[[ "$uid" -ge 1000 || "$username" == root ]] || continue
|
|
for relative_path in .ssh .openclaw .npmrc; do
|
|
copy_path "$home/$relative_path"
|
|
done
|
|
done < <(getent passwd)
|
|
else
|
|
cat > "$work_dir/metadata/excluded-secrets.txt" <<EOF
|
|
No se incluyeron secretos. Para incluirlos explícitamente, usa:
|
|
sudo $SCRIPT_NAME --include-secrets
|
|
Excluidos por defecto: /etc/ssh, /etc/ssl/private, /etc/letsencrypt, ~/.ssh,
|
|
~/.openclaw y ~/.npmrc.
|
|
EOF
|
|
fi
|
|
|
|
cat > "$work_dir/RESTORE.txt" <<EOF
|
|
Este archivo fue generado por $SCRIPT_NAME.
|
|
|
|
1. Instala Ubuntu con la misma arquitectura y crea el usuario de servicio.
|
|
2. Copia este archivo a la nueva máquina.
|
|
3. Ejecuta restore_ubuntu_config.sh con este archivo y revisa los cambios.
|
|
4. Reinstala los modelos Ollama y valida los servicios antes de exponerlos.
|
|
|
|
El respaldo no contiene modelos Ollama, /opt/litellm-env, bases de datos ni
|
|
logs completos. Si no usaste --include-secrets, debes restaurar las credenciales
|
|
manualmente.
|
|
EOF
|
|
|
|
printf 'Archivos incluidos:\n'
|
|
find "$work_dir" -type f -printf '%P\n' | sort > "$work_dir/metadata/file-list.txt"
|
|
cat "$work_dir/metadata/file-list.txt"
|
|
|
|
tar -C "$work_dir" -czf "$archive_path" .
|
|
chmod 600 "$archive_path"
|
|
printf '\nRespaldo creado: %s\n' "$archive_path"
|
|
printf 'Protección: permisos 600\n'
|