Files
calendario_contactos/radicale_users.sh
T

257 lines
6.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
USERS_FILE="${USERS_FILE:-config/users}"
DEFAULT_COST="${BCRYPT_COST:-12}"
SKIP_RESTART="${SKIP_RESTART:-0}"
# --- Utilidades -------------------------------------------------------------
usage() {
cat <<'EOF'
Gestor de usuarios Radicale
Uso:
./radicale_users.sh list
./radicale_users.sh add <usuario> [--password <pwd> | --random]
./radicale_users.sh passwd <usuario> [--password <pwd> | --random]
./radicale_users.sh delete <usuario> [--force]
./radicale_users.sh --help
Variables:
USERS_FILE Ruta del archivo htpasswd (por defecto config/users)
SKIP_RESTART Si vale 1, no reinicia el contenedor automáticamente
BCRYPT_COST Coste para htpasswd -B (por defecto 12)
Sin argumentos se abre el menú interactivo original.
EOF
}
ensure_users_file() {
if [[ ! -f "$USERS_FILE" ]]; then
echo "⚠️ No existe $USERS_FILE. Creándolo..."
mkdir -p "$(dirname "$USERS_FILE")"
touch "$USERS_FILE"
fi
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "❌ Necesitas '$1' instalado en el sistema." >&2
exit 1
fi
}
maybe_restart() {
if [[ "$SKIP_RESTART" == "1" ]]; then
echo "⚠️ SKIP_RESTART=1, no se reiniciará Radicale automáticamente."
return
fi
echo "🔄 Reiniciando contenedor Radicale..."
docker compose restart radicale >/dev/null 2>&1 || true
echo "✅ Radicale reiniciado."
}
generate_password() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -base64 18
else
date +%s | sha256sum | head -c 16
fi
}
prompt_password() {
local pwd confirm
read -rs -p "Introduce contraseña: " pwd
echo ""
read -rs -p "Confirma contraseña: " confirm
echo ""
if [[ "$pwd" != "$confirm" ]]; then
echo "❌ Las contraseñas no coinciden." >&2
exit 1
fi
echo "$pwd"
}
htpasswd_update() {
local user="$1" password="$2"
htpasswd -B -C "$DEFAULT_COST" -b "$USERS_FILE" "$user" "$password" >/dev/null
normalize_bcrypt_prefix "$user"
}
normalize_bcrypt_prefix() {
local user="$1"
if ! command -v python3 >/dev/null 2>&1; then
echo "⚠️ python3 no disponible: no se pudo normalizar el hash bcrypt de '$user' (prefijo \$2y\$)." >&2
echo " Radicale solo acepta hashes \$2b\$, por lo que este usuario podría fallar." >&2
return
fi
python3 - "$USERS_FILE" "$user" <<'PY'
import pathlib, re, sys
path = pathlib.Path(sys.argv[1])
user = sys.argv[2]
data = path.read_text()
pattern = re.compile(rf'^({re.escape(user)}:\$)2y', re.M)
updated, count = pattern.subn(r'\g<1>2b', data)
if count:
path.write_text(updated)
PY
}
list_users() {
ensure_users_file
echo "👥 Usuarios actuales:"
if [[ -s "$USERS_FILE" ]]; then
cut -d':' -f1 "$USERS_FILE" | sort
else
echo "(ninguno)"
fi
}
cmd_add() {
ensure_users_file
local user="$1" password="" random=0
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--password|-p) password="$2"; shift 2 ;;
--random|-r) random=1; shift ;;
--no-restart) SKIP_RESTART=1; shift ;;
*) echo "❗ Opción desconocida: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$user" ]]; then
echo "❌ Debes indicar un usuario." >&2
exit 1
fi
if grep -q "^$user:" "$USERS_FILE"; then
echo "⚠️ El usuario '$user' ya existe." >&2
exit 1
fi
if [[ $random -eq 1 ]]; then
password="$(generate_password)"
echo "🔐 Contraseña generada para '$user': $password"
elif [[ -z "$password" ]]; then
password="$(prompt_password)"
fi
htpasswd_update "$user" "$password"
maybe_restart
}
cmd_passwd() {
ensure_users_file
local user="$1" password="" random=0
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--password|-p) password="$2"; shift 2 ;;
--random|-r) random=1; shift ;;
--no-restart) SKIP_RESTART=1; shift ;;
*) echo "❗ Opción desconocida: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$user" ]]; then
echo "❌ Debes indicar usuario." >&2
exit 1
fi
if ! grep -q "^$user:" "$USERS_FILE"; then
echo "⚠️ El usuario '$user' no existe." >&2
exit 1
fi
if [[ $random -eq 1 ]]; then
password="$(generate_password)"
echo "🔐 Nueva contraseña generada: $password"
elif [[ -z "$password" ]]; then
password="$(prompt_password)"
fi
htpasswd_update "$user" "$password"
maybe_restart
}
cmd_delete() {
ensure_users_file
local user="$1" force=0
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--force|-f) force=1; shift ;;
--no-restart) SKIP_RESTART=1; shift ;;
*) echo "❗ Opción desconocida: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$user" ]]; then
echo "❌ Debes indicar usuario." >&2
exit 1
fi
if ! grep -q "^$user:" "$USERS_FILE"; then
echo "⚠️ El usuario '$user' no existe." >&2
exit 1
fi
if [[ $force -eq 0 ]]; then
read -rp "¿Eliminar '$user'? (s/N): " confirm
[[ "$confirm" =~ ^[sS]$ ]] || { echo "🚫 Operación cancelada."; return; }
fi
sed -i.bak "/^$user:/d" "$USERS_FILE"
rm -f "${USERS_FILE}.bak"
echo "❌ Usuario '$user' eliminado."
maybe_restart
}
# --- Menú interactivo legado -----------------------------------------------
interactive_menu() {
while true; do
echo "========================================"
echo "🔐 GESTOR DE USUARIOS RADICALE"
echo "========================================"
echo "1️⃣ Listar usuarios"
echo "2️⃣ Crear nuevo usuario"
echo "3️⃣ Editar contraseña de usuario"
echo "4️⃣ Eliminar usuario"
echo "5️⃣ Salir"
echo "----------------------------------------"
read -rp "Selecciona una opción [1-5]: " opt
echo ""
case "$opt" in
1) list_users ;;
2) read -rp "Usuario nuevo: " user; cmd_add "$user" ;;
3) read -rp "Usuario a modificar: " user; cmd_passwd "$user" ;;
4) read -rp "Usuario a eliminar: " user; cmd_delete "$user" ;;
5) echo "👋 Saliendo..."; exit 0 ;;
*) echo "❗ Opción no válida." ;;
esac
echo ""
read -rp "Presiona ENTER para continuar..." _
clear
done
}
# --- Punto de entrada -------------------------------------------------------
main() {
require_command htpasswd
if [[ $# -eq 0 ]]; then
interactive_menu
exit 0
fi
case "$1" in
list) list_users ;;
add)
shift
cmd_add "${1:-}" "${@:2}"
;;
passwd)
shift
cmd_passwd "${1:-}" "${@:2}"
;;
delete|remove)
shift
cmd_delete "${1:-}" "${@:2}"
;;
--help|-h) usage ;;
*) echo "❗ Comando desconocido: $1"; usage; exit 1 ;;
esac
}
main "$@"