Add Ubuntu configuration backup and restore scripts
This commit is contained in:
@@ -97,6 +97,60 @@ openclaw doctor
|
|||||||
openclaw gateway status
|
openclaw gateway status
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Respaldo y recuperación
|
||||||
|
|
||||||
|
Para guardar la configuración del servidor antes de realizar cambios, ejecuta
|
||||||
|
el script como `root`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x backup_ubuntu_config.sh restore_ubuntu_config.sh
|
||||||
|
sudo ./backup_ubuntu_config.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
El respaldo se guarda por defecto en `/var/backups` e incluye inventario de
|
||||||
|
hardware, paquetes, servicios, red, UFW, systemd, NVIDIA, Ollama, LiteLLM,
|
||||||
|
OpenClaw y configuraciones de usuario. No incluye modelos Ollama, el entorno
|
||||||
|
virtual de LiteLLM, bases de datos ni logs completos.
|
||||||
|
|
||||||
|
Las claves SSH, certificados privados y credenciales de OpenClaw quedan fuera
|
||||||
|
por defecto. Para incluirlos explícitamente, protege el archivo resultante:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./backup_ubuntu_config.sh --include-secrets
|
||||||
|
sudo chmod 600 /var/backups/ubuntu-config-*.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
En una instalación Ubuntu nueva, instala primero el sistema base y crea los
|
||||||
|
usuarios necesarios. Después copia el archivo de respaldo y usa el restaurador
|
||||||
|
en modo vista previa:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./restore_ubuntu_config.sh /ruta/al/respaldo.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
Para aplicar la configuración:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./restore_ubuntu_config.sh /ruta/al/respaldo.tar.gz --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Puedes reinstalar los paquetes APT y arrancar los servicios habilitados de la
|
||||||
|
máquina original de forma explícita:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./restore_ubuntu_config.sh /ruta/al/respaldo.tar.gz \
|
||||||
|
--yes --install-packages --start-services
|
||||||
|
```
|
||||||
|
|
||||||
|
Revisa siempre los servicios y el firewall después de restaurar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --failed
|
||||||
|
systemctl status ollama litellm
|
||||||
|
sudo ufw status verbose
|
||||||
|
nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
## Tool calling y rutas de Windows
|
## Tool calling y rutas de Windows
|
||||||
|
|
||||||
Cuando un modelo envía rutas de Windows dentro de argumentos JSON, las barras
|
Cuando un modelo envía rutas de Windows dentro de argumentos JSON, las barras
|
||||||
|
|||||||
Executable
+209
@@ -0,0 +1,209 @@
|
|||||||
|
#!/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'
|
||||||
|
capture_shell environment 'printenv | sort'
|
||||||
|
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'
|
||||||
Executable
+164
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
readonly SCRIPT_NAME="$(basename "$0")"
|
||||||
|
|
||||||
|
archive_path=""
|
||||||
|
confirmed=false
|
||||||
|
install_packages=false
|
||||||
|
start_services=false
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
cat <<EOF
|
||||||
|
Uso: sudo $SCRIPT_NAME ARCHIVO.tar.gz [opciones]
|
||||||
|
|
||||||
|
Restaura configuraciones de un respaldo creado por backup_ubuntu_config.sh.
|
||||||
|
Por seguridad, sin --yes solo muestra lo que se restauraría.
|
||||||
|
|
||||||
|
Opciones:
|
||||||
|
--yes Ejecuta la restauración.
|
||||||
|
--install-packages Instala los paquetes marcados como manuales en el respaldo.
|
||||||
|
--start-services Habilita y arranca los servicios que estaban habilitados.
|
||||||
|
-h, --help Muestra esta ayuda.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--yes)
|
||||||
|
confirmed=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--install-packages)
|
||||||
|
install_packages=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--start-services)
|
||||||
|
start_services=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Opción desconocida: $1" >&2
|
||||||
|
show_help >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "$archive_path" ]]; then
|
||||||
|
echo "Solo se admite un archivo de respaldo." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
archive_path="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$archive_path" ]]; then
|
||||||
|
echo "Debes indicar un archivo .tar.gz." >&2
|
||||||
|
show_help >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${EUID}" -ne 0 ]]; then
|
||||||
|
echo "Ejecuta este script con sudo o como root." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$archive_path" ]] || { echo "No existe: $archive_path" >&2; exit 1; }
|
||||||
|
command -v tar >/dev/null || { echo "tar es obligatorio." >&2; exit 1; }
|
||||||
|
|
||||||
|
work_dir="$(mktemp -d)"
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$work_dir"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
tar -xzf "$archive_path" -C "$work_dir" --no-same-owner
|
||||||
|
|
||||||
|
if [[ ! -d "$work_dir/files" || ! -f "$work_dir/metadata/backup-info.txt" ]]; then
|
||||||
|
echo "El archivo no parece un respaldo válido de backup_ubuntu_config.sh." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Respaldo: %s\n' "$archive_path"
|
||||||
|
cat "$work_dir/metadata/backup-info.txt"
|
||||||
|
printf '\nArchivos de configuración que se restaurarían:\n'
|
||||||
|
find "$work_dir/files" -mindepth 1 -maxdepth 8 -printf '%P\n' | sort | sed -n '1,200p'
|
||||||
|
|
||||||
|
if [[ "$confirmed" != true ]]; then
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Vista previa solamente. Para aplicar estos cambios:
|
||||||
|
sudo $SCRIPT_NAME "$archive_path" --yes
|
||||||
|
|
||||||
|
Opciones adicionales:
|
||||||
|
--install-packages Reinstala paquetes APT manuales.
|
||||||
|
--start-services Habilita y arranca los servicios respaldados.
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$install_packages" == true && -s "$work_dir/packages/apt-manual.txt" ]]; then
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update
|
||||||
|
xargs -r apt-get install -y --no-install-recommends < "$work_dir/packages/apt-manual.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$work_dir/files/etc" ]]; then
|
||||||
|
cp -a "$work_dir/files/etc/." /etc/
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$work_dir/files/home" ]]; then
|
||||||
|
cp -a "$work_dir/files/home/." /home/
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$work_dir/files/root" ]]; then
|
||||||
|
cp -a "$work_dir/files/root/." /root/
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s "$work_dir/metadata/users.tsv" ]]; then
|
||||||
|
while IFS=$'\t' read -r username _ _ home; do
|
||||||
|
[[ -n "$username" && -d "$home" ]] || continue
|
||||||
|
if id "$username" >/dev/null 2>&1; then
|
||||||
|
chown -R "$username:$(id -gn "$username")" "$home" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "Aviso: no existe el usuario $username; revisa $home manualmente." >&2
|
||||||
|
fi
|
||||||
|
done < "$work_dir/metadata/users.tsv"
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
if [[ "$start_services" == true && -s "$work_dir/services/enabled-service-names.txt" ]]; then
|
||||||
|
while IFS= read -r service; do
|
||||||
|
[[ -n "$service" ]] || continue
|
||||||
|
systemctl enable "$service" 2>/dev/null || echo "Aviso: no se pudo habilitar $service" >&2
|
||||||
|
done < "$work_dir/services/enabled-service-names.txt"
|
||||||
|
|
||||||
|
systemctl restart ollama 2>/dev/null || true
|
||||||
|
systemctl restart litellm 2>/dev/null || true
|
||||||
|
systemctl restart openclaw-gateway 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v ufw >/dev/null 2>&1; then
|
||||||
|
ufw --force reload 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Restauración completada.
|
||||||
|
|
||||||
|
Revisa antes de exponer el servidor:
|
||||||
|
systemctl --failed
|
||||||
|
systemctl status ollama litellm
|
||||||
|
ufw status verbose
|
||||||
|
nvidia-smi
|
||||||
|
|
||||||
|
Los modelos Ollama y el entorno virtual de LiteLLM no forman parte del respaldo.
|
||||||
|
Debes reinstalarlos o descargarlos por separado.
|
||||||
|
EOF
|
||||||
Reference in New Issue
Block a user