diff options
| author | luisgulo <luisgulo@gmail.com> | 2025-10-24 18:01:10 +0200 |
|---|---|---|
| committer | luisgulo <luisgulo@gmail.com> | 2025-10-24 18:01:10 +0200 |
| commit | 533e79ba959143f0459431a486bfb85c56c72ddc (patch) | |
| tree | 91974de1bbbdc4c51c76ed591fc5c6e02a3342b6 /core/utils | |
| parent | 45019c81cfd0fc1d18dce18cdfd5f127c6d61073 (diff) | |
Releasing code version 1.8.0
Diffstat (limited to 'core/utils')
25 files changed, 799 insertions, 0 deletions
diff --git a/core/utils/eg.sh b/core/utils/eg.sh new file mode 100755 index 0000000..551c9d0 --- /dev/null +++ b/core/utils/eg.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +Qes="8J+lmiBIYXMgZW5jb250cmFkbyBlbCBodWV2byBkZSBwYXNjdWEuIMKhQnVlbiBvam8h·4pyoIE5vIHRvZG9zIGxvcyBzY3JpcHRzIHRpZW5lbiBhbG1hLi4uIHBlcm8gZXN0ZSBzw60u·8J+noCBFbCBtZWpvciBidWcgZXMgZWwgcXVlIG51bmNhIGV4aXN0acOzLg==·8J+QoyBTaEZsb3cgdGUgc2FsdWRhIGRlc2RlIGxhcyBzb21icmFzLg==·8J+TnCBMYSBhdXRvbWF0aXphY2nDs24gdGFtYmnDqW4gdGllbmUgcG9lc8OtYS4=" +Qen="8J+lmiBZb3UgZm91bmQgdGhlIEVhc3RlciBlZ2cuIFNoYXJwIGV5ZSE=·4pyoIE5vdCBhbGwgc2NyaXB0cyBoYXZlIHNvdWwuLi4gYnV0IHRoaXMgb25lIGRvZXMu·8J+noCBUaGUgYmVzdCBidWcgaXMgdGhlIG9uZSB0aGF0IG5ldmVyIGV4aXN0ZWQu·8J+QoyBTaEZsb3cgZ3JlZXRzIHlvdSBmcm9tIHRoZSBzaGFkb3dzLg==·8J+TnCBBdXRvbWF0aW9uIGhhcyBwb2V0cnkgdG9vLg==" +vhs=([0]="448b55f2" [1]="c9f66247" [2]="154f020f" [3]="0e931208" [4]="d2e2fa57") + +sheg() { + local P="$1" ; P=$((P+1)) + echo "――――――" + echo "$Qes" |awk -F '·' -v p="$P" '{print $p}' | base64 -d; echo + echo "$Qen" |awk -F '·' -v p="$P" '{print $p}' | base64 -d; echo + echo "――――――" +} + +main() { + [[ $# -lt 1 ]] && return 0 + local input="$1" ; local hsh + hsh=$(echo -n "$input" | md5sum | cut -c1-8) + for n in "${!vhs[@]}"; do + if [[ "$hsh" == "${vhs[$n]}" ]]; then + sheg "$n" ; break + fi + done + return 0 +} + +main "$@" diff --git a/core/utils/module-docgen.sh b/core/utils/module-docgen.sh new file mode 100755 index 0000000..f5461cd --- /dev/null +++ b/core/utils/module-docgen.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# ShFlow Module Documentation Generator +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.4.0 + +set -euo pipefail + +# 🧭 Detección de la raíz del proyecto +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +OUTPUT="$PROJECT_ROOT/docs/modules-list.md" +MODULE_DIRS=("$PROJECT_ROOT/core/modules" "$PROJECT_ROOT/user_modules" "$PROJECT_ROOT/community_modules") + +export SHFLOW_LANG="${SHFLOW_LANG:-es}" + +# 🧩 Cargar render_msg si no está disponible +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +# 🌐 Cargar traducciones +lang="${SHFLOW_LANG:-es}" + +trfile="$PROJECT_ROOT/core/utils/module-docgen.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +# 📝 Encabezado del documento +{ + echo "${tr[title]:-# 🧩 Módulos en ShFlow}" + echo "" + echo "**$(render_msg "${tr[generated]}" "date=$(date '+%Y-%m-%d %H:%M:%S')")**" + echo "" + echo "| ${tr[col_module]:-Módulo} | ${tr[col_desc]:-Descripción} | ${tr[col_type]:-Tipo} | ${tr[col_author]:-Autor} | ${tr[col_version]:-Versión} | ${tr[col_deps]:-Dependencias} |" + echo "|--------|-------------|------|-------|---------|--------------|" +} > "$OUTPUT" + +# 🔁 Procesar módulos +for dir in "${MODULE_DIRS[@]}"; do + [ -d "$dir" ] || continue + TYPE=$(echo "$dir" | sed "s#$PROJECT_ROOT/##g") + while IFS= read -r -d '' file; do + name=$(basename "$file" .sh) + desc=$(grep -E '^# Description:' "$file" | sed 's/^# Description:[[:space:]]*//') + author=$(grep -E '^# Author:' "$file" | sed 's/^# Author:[[:space:]]*//') + version=$(grep -E '^# Version:' "$file" | sed 's/^# Version:[[:space:]]*//') + deps=$(grep -E '^# Dependencies:' "$file" | sed 's/^# Dependencies:[[:space:]]*//') + + # Asegurar valor minimo + name=${name:-""} + desc=${desc:-""} + author=${author:-""} + version=${version:-""} + deps=${deps:-""} + + [[ -z "$name" ]] && continue + + echo "| $name | $desc | $TYPE | $author | $version | $deps |" >> "$OUTPUT" + done < <(find "$dir" -type f -name "*.sh" -print0) +done + +# 📌 Pie de página +{ + echo "" + echo "${tr[footer]:-_Para actualizar esta tabla, ejecuta: \`module-docgen\`_}" +} >> "$OUTPUT" + +echo "$(render_msg "${tr[done]}" "path=$OUTPUT")" diff --git a/core/utils/module-docgen.tr.en b/core/utils/module-docgen.tr.en new file mode 100644 index 0000000..644b2c3 --- /dev/null +++ b/core/utils/module-docgen.tr.en @@ -0,0 +1,10 @@ +title=# 🧩 Modules in ShFlow +generated=Automatically generated on {date} +col_module=Module +col_desc=Description +col_type=Type +col_author=Author +col_version=Version +col_deps=Dependencies +footer=_To update this table, run: `module-docgen`_ +done=✅ Documentation generated at {path} diff --git a/core/utils/module-docgen.tr.es b/core/utils/module-docgen.tr.es new file mode 100644 index 0000000..14b68fa --- /dev/null +++ b/core/utils/module-docgen.tr.es @@ -0,0 +1,10 @@ +title=# 🧩 Módulos en ShFlow +generated=Generado automáticamente el {date} +col_module=Módulo +col_desc=Descripción +col_type=Tipo +col_author=Autor +col_version=Versión +col_deps=Dependencias +footer=_Para actualizar esta tabla, ejecuta: `module-docgen`_ +done=✅ Documentación generada en {path} diff --git a/core/utils/module-template.sh b/core/utils/module-template.sh new file mode 100755 index 0000000..a38624b --- /dev/null +++ b/core/utils/module-template.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# ShFlow Module Template Generator +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.1.0 + +set -euo pipefail + +# 📁 Rutas defensivas +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +MODULE_NAME="${1:-}" +MODULE_DIR="$PROJECT_ROOT/core/modules" +MODULE_FILE="$MODULE_DIR/$MODULE_NAME.sh" + +# 🧩 Cargar render_msg si no está disponible +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +# 🌐 Cargar traducciones +lang="${SHFLOW_LANG:-es}" +trfile="$PROJECT_ROOT/core/utils/module-template.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +# 🧪 Validar entrada +if [[ -z "$MODULE_NAME" ]]; then + echo "${tr[usage]:-❌ Uso: module-template.sh <nombre_modulo>}" + exit 1 +fi + +if [[ -f "$MODULE_FILE" ]]; then + echo "$(render_msg "${tr[exists]}" "name=$MODULE_NAME" "dir=$MODULE_DIR")" + exit 1 +fi + +mkdir -p "$MODULE_DIR" + +cat > "$MODULE_FILE" <<EOF +#!/bin/bash +# Module: $MODULE_NAME +# Description: <descripción breve del módulo> +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.0 +# Dependencies: <comandos externos si aplica> + +${MODULE_NAME}_task() { + local host="\$1"; shift + declare -A args + for arg in "\$@"; do + key="\${arg%%=*}" + value="\${arg#*=}" + args["\$key"]="\$value" + done + + echo "🚧 Ejecutando módulo '$MODULE_NAME' en \$host" + # Aquí va la lógica principal +} + +check_dependencies_$MODULE_NAME() { + # Verifica herramientas necesarias + for cmd in <comando1> <comando2>; do + if ! command -v "\$cmd" &> /dev/null; then + echo " ❌ [$MODULE_NAME] Falta: \$cmd" + return 1 + fi + done + echo " ✅ [$MODULE_NAME] Dependencias OK" + return 0 +} +EOF + +chmod +x "$MODULE_FILE" +echo "$(render_msg "${tr[created]}" "name=$MODULE_NAME" "path=$MODULE_FILE")" diff --git a/core/utils/module-template.tr.en b/core/utils/module-template.tr.en new file mode 100644 index 0000000..bc948d7 --- /dev/null +++ b/core/utils/module-template.tr.en @@ -0,0 +1,3 @@ +usage=❌ Usage: module-template.sh <module_name> +exists=⚠️ Module '{name}' already exists in {dir} +created=✅ Module '{name}' created at {path} diff --git a/core/utils/module-template.tr.es b/core/utils/module-template.tr.es new file mode 100644 index 0000000..4dc6e63 --- /dev/null +++ b/core/utils/module-template.tr.es @@ -0,0 +1,3 @@ +usage=❌ Uso: module-template.sh <nombre_modulo> +exists=⚠️ El módulo '{name}' ya existe en {dir} +created=✅ Módulo '{name}' creado en {path} diff --git a/core/utils/shflow-check.sh b/core/utils/shflow-check.sh new file mode 100755 index 0000000..130eca0 --- /dev/null +++ b/core/utils/shflow-check.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# ShFlow Environment Checker +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.5.0 + +set -e + +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" + +MODULE_PATHS=( + "$PROJECT_ROOT/core/modules" + "$PROJECT_ROOT/user_modules" + "$PROJECT_ROOT/community_modules" +) + +# 🧩 Cargar funciones comunes si no están disponibles +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +GLOBAL_TOOLS=("bash" "ssh" "scp" "git" "curl" "jq" "yq" "gpg") + +REQUIRED_PATHS=( + "$PROJECT_ROOT/core/modules" + "$PROJECT_ROOT/core/utils" + "$PROJECT_ROOT/core/inventory" + "$PROJECT_ROOT/examples" + "$PROJECT_ROOT/user_modules" + "$PROJECT_ROOT/community_modules" + "$PROJECT_ROOT/shflow.sh" + "$PROJECT_ROOT/vault.sh" +) + +# 🌐 Cargar traducciones +lang="${shflow_vars[language]:-es}" +trfile="$PROJECT_ROOT/core/utils/shflow-check.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +check_global_tools() { + echo "${tr[tools_header]:-🔍 Verificando herramientas globales...}" + local missing=0 + for tool in "${GLOBAL_TOOLS[@]}"; do + if ! command -v "$tool" &> /dev/null; then + echo "$(render_msg "${tr[tool_missing]}" "tool=$tool")" + missing=1 + else + echo "$(render_msg "${tr[tool_ok]}" "tool=$tool")" + fi + done + return $missing +} + +check_structure() { + echo "" + echo "${tr[structure_header]:-📁 Verificando estructura de ShFlow...}" + local missing=0 + for path in "${REQUIRED_PATHS[@]}"; do + if [ ! -e "$path" ]; then + echo "$(render_msg "${tr[path_missing]}" "path=$path")" + missing=1 + else + echo "$(render_msg "${tr[path_ok]}" "path=$path")" + fi + done + return $missing +} + +load_and_check_modules() { + echo "" + echo "${tr[modules_header]:-🔍 Verificando módulos ShFlow...}" + for dir in "${MODULE_PATHS[@]}"; do + [ -d "$dir" ] || continue + while IFS= read -r -d '' mod; do + source "$mod" + done < <(find "$dir" -type f -name "*.sh" -print0) + done + + for func in $(declare -F | awk '{print $3}' | grep '^check_dependencies_'); do + echo "" + echo "$(render_msg "${tr[checking_func]}" "func=$func")" + $func || echo "$(render_msg "${tr[func_warn]}" "func=$func")" + done +} + +main() { + echo "${tr[title]:-🧪 ShFlow Environment Check}" + echo "${tr[separator]:-=============================}" + + check_global_tools + check_structure + load_and_check_modules + + echo "" + echo "${tr[done]:-✅ Verificación completada.}" +} + +main "$@" diff --git a/core/utils/shflow-check.tr.en b/core/utils/shflow-check.tr.en new file mode 100644 index 0000000..6430ffd --- /dev/null +++ b/core/utils/shflow-check.tr.en @@ -0,0 +1,12 @@ +title=🧪 ShFlow Environment Check +separator=_____________________________ +tools_header=🔍 Checking global tools... +tool_missing=❌ {tool} not found +tool_ok=✅ {tool} available +structure_header=📁 Checking ShFlow structure... +path_missing=❌ Missing: {path} +path_ok=✅ Found: {path} +modules_header=🔍 Checking ShFlow modules... +checking_func=🔧 Running {func}... +func_warn=⚠️ Incomplete dependencies in {func} +done=✅ Check completed. diff --git a/core/utils/shflow-check.tr.es b/core/utils/shflow-check.tr.es new file mode 100644 index 0000000..983f318 --- /dev/null +++ b/core/utils/shflow-check.tr.es @@ -0,0 +1,12 @@ +title=🧪 ShFlow Environment Check +separator=_____________________________ +tools_header=🔍 Verificando herramientas globales... +tool_missing=❌ {tool} no encontrado +tool_ok=✅ {tool} disponible +structure_header=📁 Verificando estructura de ShFlow... +path_missing=❌ Falta: {path} +path_ok=✅ Encontrado: {path} +modules_header=🔍 Verificando módulos ShFlow... +checking_func=🔧 Ejecutando {func}... +func_warn=⚠️ Dependencias incompletas en {func} +done=✅ Verificación completada. diff --git a/core/utils/shflow-doc.sh b/core/utils/shflow-doc.sh new file mode 100755 index 0000000..861f1b9 --- /dev/null +++ b/core/utils/shflow-doc.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# ShFlow Doc Generator +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.1.0 + +set -e + +# ───────────────────────────────────────────── +# 🧭 Detección de la raíz del proyecto +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" + +# 🧩 Cargar funciones comunes si no están disponibles +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +# 🌐 Cargar traducciones +lang="${shflow_vars[language]:-es}" +trfile="$PROJECT_ROOT/core/utils/shflow-doc.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +MODULE_PATHS=( + "$PROJECT_ROOT/core/modules" + "$PROJECT_ROOT/user_modules" + "$PROJECT_ROOT/community_modules" +) + +extract_metadata() { + local file="$1" + local module desc author version deps + + module=$(grep -m1 '^# Module:' "$file" | cut -d':' -f2- | xargs) + desc=$(grep -m1 '^# Description:' "$file" | cut -d':' -f2- | xargs) + author=$(grep -m1 '^# Author:' "$file" | cut -d':' -f2- | xargs) + version=$(grep -m1 '^# Version:' "$file" | cut -d':' -f2- | xargs) + deps=$(grep -m1 '^# Dependencies:' "$file" | cut -d':' -f2- | xargs) + + echo "$(render_msg "${tr[module]}" "name=$module")" + echo "$(render_msg "${tr[desc]}" "desc=$desc")" + echo "$(render_msg "${tr[author]}" "author=$author")" + echo "$(render_msg "${tr[version]}" "version=$version")" + echo "$(render_msg "${tr[deps]}" "deps=$deps")" + echo "${tr[separator]:- ————————————————————————}" +} + +main() { + echo "${tr[title]:-📚 ShFlow Modules Documentation}" + echo "${tr[separator_line]:-=================================}" + + for dir in "${MODULE_PATHS[@]}"; do + [ -d "$dir" ] || continue + ROUTE=$(echo "$dir" | sed "s#$PROJECT_ROOT/##g") + echo -e "\n$(render_msg "${tr[section]}" "type=$ROUTE")" + while IFS= read -r -d '' file; do + extract_metadata "$file" + done < <(find "$dir" -type f -name "*.sh" -print0) + done +} + +main "$@" diff --git a/core/utils/shflow-doc.tr.en b/core/utils/shflow-doc.tr.en new file mode 100644 index 0000000..5d3167e --- /dev/null +++ b/core/utils/shflow-doc.tr.en @@ -0,0 +1,9 @@ +title=📚 ShFlow Modules Documentation +separator_line================================== +section=🗃️ Module Type: {type} +module= 📦 Module: {name} +desc= 🔧 Description: {desc} +author= 👤 Author: {author} +version= 📌 Version: {version} +deps= 📎 Dependencies: {deps} +separator= ———————————————————————— diff --git a/core/utils/shflow-doc.tr.es b/core/utils/shflow-doc.tr.es new file mode 100644 index 0000000..b1150f7 --- /dev/null +++ b/core/utils/shflow-doc.tr.es @@ -0,0 +1,9 @@ +title=📚 ShFlow Modules Documentation +separator_line================================== +section=🗃️ Tipo de módulo: {type} +module= 📦 Módulo: {name} +desc= 🔧 Descripción: {desc} +author= 👤 Autor: {author} +version= 📌 Versión: {version} +deps= 📎 Dependencias: {deps} +separator= ———————————————————————— diff --git a/core/utils/shflow-ssh-init.sh b/core/utils/shflow-ssh-init.sh new file mode 100755 index 0000000..26d5e70 --- /dev/null +++ b/core/utils/shflow-ssh-init.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Utility: shflow-ssh-init +# Description: Inicializa acceso SSH sin contraseña en los hosts del inventario +# Author: Luis GuLo +# Version: 0.2.0 + +set -euo pipefail + +# 📁 Rutas defensivas +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" +INVENTORY="$PROJECT_ROOT/core/inventory/hosts.yaml" +TIMEOUT=5 +USER="${USER:-$(whoami)}" +KEY="${KEY:-$HOME/.ssh/id_rsa.pub}" + +# 🧩 Cargar render_msg si no está disponible +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +export SHFLOW_LANG="${SHFLOW_LANG:-es}" +# 🌐 Cargar traducciones +lang="${SHFLOW_LANG:-es}" + +trfile="$PROJECT_ROOT/core/utils/shflow-ssh-init.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +echo "$(render_msg "${tr[start]}" "user=$USER")" +echo "$(render_msg "${tr[inventory]}" "path=$INVENTORY")" +echo "$(render_msg "${tr[key]}" "key=$KEY")" +echo "" + +# 🧪 Validar dependencias +for cmd in yq ssh ssh-copy-id; do + if ! command -v "$cmd" &>/dev/null; then + echo "$(render_msg "${tr[missing_dep]}" "cmd=$cmd")" + exit 1 + fi +done + +# 🔁 Extraer hosts +HOSTS=() +HOSTS_RAW=$(yq ".all.hosts | keys | .[]" "$INVENTORY") +[ -z "$HOSTS_RAW" ] && echo "${tr[no_hosts]:-❌ No se encontraron hosts en el inventario.}" && exit 1 + +while IFS= read -r line; do + HOSTS+=("$(echo "$line" | sed 's/^\"\(.*\)\"$/\1/')") # Eliminar comillas +done <<< "$HOSTS_RAW" + +# 🔍 Evaluar cada host +for host in "${HOSTS[@]}"; do + IP=$(yq -r ".all.hosts.\"$host\".ansible_host" "$INVENTORY") + [[ "$IP" == "null" || -z "$IP" ]] && echo "$(render_msg "${tr[missing_ip]}" "host=$host")" && continue + + echo "$(render_msg "${tr[checking]}" "host=$host" "ip=$IP")" + + if ssh -o BatchMode=yes -o ConnectTimeout=$TIMEOUT "$USER@$IP" 'true' &>/dev/null; then + echo "${tr[skip]:- 🔁 Inicialización SSH no es necesaria}" + continue + fi + + echo "$(render_msg "${tr[copying]}" "user=$USER" "ip=$IP")" + if ssh-copy-id -i "$KEY" "$USER@$IP"; then + echo "${tr[success]:- ✅ Clave pública instalada correctamente}" + else + echo "${tr[fail]:- ❌ Fallo al instalar clave pública}" + fi + + echo "" +done + +echo "${tr[done]:-✅ Proceso de inicialización SSH completado}" diff --git a/core/utils/shflow-ssh-init.tr.en b/core/utils/shflow-ssh-init.tr.en new file mode 100644 index 0000000..1005b42 --- /dev/null +++ b/core/utils/shflow-ssh-init.tr.en @@ -0,0 +1,12 @@ +start=🔐 Initializing passwordless SSH access for user: {user} +inventory=📁 Inventory: {path} +key=🔑 Public key: {key} +missing_dep=❌ Requires '{cmd}' installed on the system +no_hosts=❌ No hosts found in inventory. +missing_ip=⚠️ Host '{host}' has no ansible_host defined +checking=🖥️ Host: {host} ({ip}) +skip= 🔁 SSH initialization not needed +copying= 🚀 Running ssh-copy-id for {user}@{ip} +success= ✅ Public key installed successfully +fail= ❌ Failed to install public key +done=✅ SSH initialization process completed diff --git a/core/utils/shflow-ssh-init.tr.es b/core/utils/shflow-ssh-init.tr.es new file mode 100644 index 0000000..c693c8f --- /dev/null +++ b/core/utils/shflow-ssh-init.tr.es @@ -0,0 +1,12 @@ +start=🔐 Inicializando acceso SSH sin contraseña para usuario: {user} +inventory=📁 Inventario: {path} +key=🔑 Clave pública: {key} +missing_dep=❌ Requiere '{cmd}' instalado en el sistema +no_hosts=❌ No se encontraron hosts en el inventario. +missing_ip=⚠️ Host '{host}' sin ansible_host definido +checking=🖥️ Host: {host} ({ip}) +skip= 🔁 Inicialización SSH no es necesaria +copying= 🚀 Ejecutando ssh-copy-id para {user}@{ip} +success= ✅ Clave pública instalada correctamente +fail= ❌ Fallo al instalar clave pública +done=✅ Proceso de inicialización SSH completado diff --git a/core/utils/shflow-trust.sh b/core/utils/shflow-trust.sh new file mode 100755 index 0000000..bc27d94 --- /dev/null +++ b/core/utils/shflow-trust.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Utility: shflow-trust +# Description: Evalúa acceso SSH y privilegios sudo para cada host del inventario +# Author: Luis GuLo +# Version: 0.4.0 + +set -euo pipefail + +# 📁 Rutas defensivas +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" +INVENTORY="$PROJECT_ROOT/core/inventory/hosts.yaml" +REPORT="$PROJECT_ROOT/core/inventory/trust_report.yaml" +TIMEOUT=5 +USER="${USER:-$(whoami)}" + +# 🧩 Cargar render_msg si no está disponible +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +export SHFLOW_LANG="${SHFLOW_LANG:-es}" +# 🌐 Cargar traducciones +lang="${SHFLOW_LANG:-es}" + +trfile="$PROJECT_ROOT/core/utils/shflow-trust.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +echo "$(render_msg "${tr[start]}" "user=$USER")" +echo "$(render_msg "${tr[inventory]}" "path=$INVENTORY")" +echo "$(render_msg "${tr[report]}" "path=$REPORT")" +echo "" + +# 🧪 Validar dependencia yq +if ! command -v yq &>/dev/null; then + echo "$(render_msg "${tr[missing_dep]}" "cmd=yq")" + exit 1 +fi + +# 🧹 Regenerar informe +{ + echo "# $(render_msg "${tr[report_title]}")" + echo "# $(render_msg "${tr[report_date]}" "date=$(date)")" + echo "" +} > "$REPORT" + +# 🔁 Extraer hosts +HOSTS=() +HOSTS_RAW=$(yq ".all.hosts | keys | .[]" "$INVENTORY") +[ -z "$HOSTS_RAW" ] && echo "${tr[no_hosts]:-❌ No se encontraron hosts en el inventario.}" && exit 1 + +while IFS= read -r line; do + HOSTS+=("$(echo "$line" | sed 's/^\"\(.*\)\"$/\1/')") # Eliminar comillas +done <<< "$HOSTS_RAW" + +# 🔍 Evaluar cada host +for host in "${HOSTS[@]}"; do + IP=$(yq -r ".all.hosts.\"$host\".ansible_host" "$INVENTORY") + [[ "$IP" == "null" || -z "$IP" ]] && echo "$(render_msg "${tr[missing_ip]}" "host=$host")" && continue + + echo "$(render_msg "${tr[checking]}" "host=$host" "ip=$IP")" + + if ssh -o BatchMode=yes -o ConnectTimeout=$TIMEOUT "$USER@$IP" 'echo ok' &>/dev/null; then + echo "${tr[ssh_ok]:- ✅ SSH: ok}" + SSH_STATUS="ok" + + if ssh -o BatchMode=yes "$USER@$IP" 'sudo -n true' &>/dev/null; then + echo "${tr[sudo_ok]:- ✅ SUDO: ok}" + SUDO_STATUS="ok" + else + echo "${tr[sudo_pw]:- ⚠️ SUDO: requiere contraseña o no permitido}" + SUDO_STATUS="password_required" + fi + else + echo "${tr[ssh_fail]:- ❌ SSH: fallo de conexión}" + SSH_STATUS="failed" + SUDO_STATUS="unknown" + fi + + { + echo "$host:" + echo " ip: $IP" + echo " ssh: $SSH_STATUS" + echo " sudo: $SUDO_STATUS" + echo "" + } >> "$REPORT" +done + +echo "$(render_msg "${tr[done]}" "path=$REPORT")" diff --git a/core/utils/shflow-trust.tr.en b/core/utils/shflow-trust.tr.en new file mode 100644 index 0000000..5e1209f --- /dev/null +++ b/core/utils/shflow-trust.tr.en @@ -0,0 +1,14 @@ +start=🔍 Evaluating SSH and sudo trust for user: {user} +inventory=📁 Inventory: {path} +report=📄 Report: {path} +missing_dep=❌ Requires '{cmd}' (Go version) to process YAML inventory +report_title=Trust report generated by shflow-trust +report_date=Date: {date} +no_hosts=❌ No hosts found in inventory. +missing_ip=⚠️ Host '{host}' has no ansible_host defined +checking=🖥️ Host: {host} ({ip}) +ssh_ok= ✅ SSH: ok +ssh_fail= ❌ SSH: connection failed +sudo_ok= ✅ SUDO: ok +sudo_pw= ⚠️ SUDO: requires password or not allowed +done=✅ Report completed: {path} diff --git a/core/utils/shflow-trust.tr.es b/core/utils/shflow-trust.tr.es new file mode 100644 index 0000000..9e930a9 --- /dev/null +++ b/core/utils/shflow-trust.tr.es @@ -0,0 +1,14 @@ +start=🔍 Evaluando confianza SSH y sudo para usuario: {user} +inventory=📁 Inventario: {path} +report=📄 Informe: {path} +missing_dep=❌ Requiere '{cmd}' versión Go para procesar el inventario YAML +report_title=Informe de confianza generado por shflow-trust +report_date=Fecha: {date} +no_hosts=❌ No se encontraron hosts en el inventario. +missing_ip=⚠️ Host '{host}' sin ansible_host definido +checking=🖥️ Host: {host} ({ip}) +ssh_ok= ✅ SSH: ok +ssh_fail= ❌ SSH: fallo de conexión +sudo_ok= ✅ SUDO: ok +sudo_pw= ⚠️ SUDO: requiere contraseña o no permitido +done=✅ Informe completado: {path} diff --git a/core/utils/vault-init.sh b/core/utils/vault-init.sh new file mode 100755 index 0000000..3055a10 --- /dev/null +++ b/core/utils/vault-init.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# ShFlow Vault Initializer +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.3.0 +# Dependencies: gpg + +set -euo pipefail + +# 📁 Rutas defensivas +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" +VAULT_DIR="$PROJECT_ROOT/core/vault" +VAULT_KEY="${VAULT_KEY:-$HOME/.shflow.key}" +VAULT_PUBKEY="${VAULT_PUBKEY:-$HOME/.shflow.pub}" + +# 🧩 Cargar render_msg si no está disponible +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +# 🌐 Cargar traducciones +lang="${SHFLOW_LANG:-es}" +trfile="$PROJECT_ROOT/core/utils/vault-init.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +generate_key() { + echo "${tr[gen_key]:-🔐 Generando nueva clave simétrica...}" + head -c 64 /dev/urandom | base64 > "$VAULT_KEY" + chmod 600 "$VAULT_KEY" + echo "$(render_msg "${tr[key_created]}" "path=$VAULT_KEY")" +} + +rotate_key() { + echo "${tr[rotate_start]:-🔄 Rotando clave y re-cifrando secretos...}" + local OLD_KEY="$VAULT_KEY.old" + + cp "$VAULT_KEY" "$OLD_KEY" + generate_key + + for file in "$VAULT_DIR"/*.gpg; do + key=$(basename "$file" .gpg) + echo "$(render_msg "${tr[recrypt]}" "key=$key")" + gpg --quiet --batch --yes --passphrase-file "$OLD_KEY" -d "$file" | \ + gpg --symmetric --batch --yes --passphrase-file "$VAULT_KEY" -o "$VAULT_DIR/$key.gpg.new" + mv "$VAULT_DIR/$key.gpg.new" "$VAULT_DIR/$key.gpg" + done + + echo "$(render_msg "${tr[rotate_done]}" "path=$OLD_KEY")" +} + +status() { + echo "${tr[status_title]:-📊 Estado del Vault}" + echo "-------------------" + echo "$(render_msg "${tr[sym_key]}" "status=$( [ -f "$VAULT_KEY" ] && echo "${tr[present]}" || echo "${tr[absent]}")")" + echo "$(render_msg "${tr[pub_key]}" "status=$( [ -f "$VAULT_PUBKEY" ] && echo "${tr[present]}" || echo "${tr[absent]}")")" + echo "$(render_msg "${tr[vault_path]}" "path=$VAULT_DIR")" + echo "$(render_msg "${tr[secrets]}" "count=$(ls "$VAULT_DIR"/*.gpg 2>/dev/null | wc -l)")" + echo "$(render_msg "${tr[last_mod]}" "date=$(date -r "$VAULT_KEY" '+%Y-%m-%d %H:%M:%S' 2>/dev/null)")" +} + +generate_pubkey() { + echo "${tr[asym_start]:-🔐 Configurando cifrado asimétrico...}" + echo "${tr[asym_hint]:-⚠️ Se requiere que la clave pública esté exportada previamente.}" + echo " gpg --export -a 'usuario@dominio' > $VAULT_PUBKEY" + if [ -f "$VAULT_PUBKEY" ]; then + echo "$(render_msg "${tr[pubkey_found]}" "path=$VAULT_PUBKEY")" + else + echo "${tr[pubkey_missing]:-❌ Clave pública no encontrada. Exporta primero con GPG.}" + exit 1 + fi +} + +main() { + case "${1:-}" in + --rotate) + [ -f "$VAULT_KEY" ] || { echo "${tr[no_key]:-❌ No existe clave actual. Ejecuta sin --rotate primero.}"; exit 1; } + rotate_key + ;; + --status) + status + ;; + --asymmetric) + generate_pubkey + ;; + *) + if [ -f "$VAULT_KEY" ]; then + echo "$(render_msg "${tr[key_exists]}" "path=$VAULT_KEY")" + else + generate_key + fi + ;; + esac +} + +main "$@" diff --git a/core/utils/vault-init.tr.en b/core/utils/vault-init.tr.en new file mode 100644 index 0000000..8004348 --- /dev/null +++ b/core/utils/vault-init.tr.en @@ -0,0 +1,19 @@ +gen_key=🔐 Generating new symmetric key... +key_created=✅ Key created at {path} +rotate_start=🔄 Rotating key and re-encrypting secrets... +recrypt=🔁 Re-encrypting '{key}'... +rotate_done=✅ Rotation completed. Old key saved at {path} +status_title=📊 Vault Status +sym_key=🔐 Symmetric key: {status} +pub_key=🔐 Public key: {status} +present=✅ present +absent=❌ absent +vault_path=📁 Vault path: {path} +secrets=📦 Secrets: {count} +last_mod=🕒 Last modified: {date} +asym_start=🔐 Setting up asymmetric encryption... +asym_hint=⚠️ Public key must be exported beforehand. +pubkey_found=✅ Public key found at {path} +pubkey_missing=❌ Public key not found. Export it first using GPG. +no_key=❌ No current key found. Run without --rotate first. +key_exists=🔐 Key already exists at {path} diff --git a/core/utils/vault-init.tr.es b/core/utils/vault-init.tr.es new file mode 100644 index 0000000..8ed9644 --- /dev/null +++ b/core/utils/vault-init.tr.es @@ -0,0 +1,19 @@ +gen_key=🔐 Generando nueva clave simétrica... +key_created=✅ Clave creada en {path} +rotate_start=🔄 Rotando clave y re-cifrando secretos... +recrypt=🔁 Re-cifrando '{key}'... +rotate_done=✅ Rotación completada. Clave antigua guardada en {path} +status_title=📊 Estado del Vault +sym_key=🔐 Clave simétrica: {status} +pub_key=🔐 Clave pública: {status} +present=✅ presente +absent=❌ ausente +vault_path=📁 Ruta del vault: {path} +secrets=📦 Secretos: {count} +last_mod=🕒 Última modificación: {date} +asym_start=🔐 Configurando cifrado asimétrico... +asym_hint=⚠️ Se requiere que la clave pública esté exportada previamente. +pubkey_found=✅ Clave pública detectada en {path} +pubkey_missing=❌ Clave pública no encontrada. Exporta primero con GPG. +no_key=❌ No existe clave actual. Ejecuta sin --rotate primero. +key_exists=🔐 Clave ya existe en {path} diff --git a/core/utils/vault_utils.sh b/core/utils/vault_utils.sh new file mode 100755 index 0000000..702265f --- /dev/null +++ b/core/utils/vault_utils.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Utility: vault_utils +# Description: Funciones para acceso seguro al vault de ShFlow +# License: GPLv3 +# Author: Luis GuLo +# Version: 1.1.0 +# Dependencies: gpg + +VAULT_DIR="${VAULT_DIR:-core/vault}" +VAULT_KEY="${VAULT_KEY:-$HOME/.shflow.key}" + +# 🧩 Cargar render_msg si no está disponible +PROJECT_ROOT="${SHFLOW_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +COMMON_LIB="$PROJECT_ROOT/core/lib/translate_msg.sh" +if ! declare -f render_msg &>/dev/null; then + [[ -f "$COMMON_LIB" ]] && source "$COMMON_LIB" +fi + +# 🌐 Cargar traducciones +lang="${SHFLOW_LANG:-es}" +trfile="$PROJECT_ROOT/core/utils/vault_utils.tr.${lang}" +declare -A tr +if [[ -f "$trfile" ]]; then while IFS='=' read -r k v; do tr["$k"]="$v"; done < "$trfile"; fi + +get_secret() { + local key="$1" + local value + + if [ ! -f "$VAULT_DIR/$key.gpg" ]; then + echo "$(render_msg "${tr[missing]}" "key=$key" "dir=$VAULT_DIR")" + return 1 + fi + + value=$(gpg --quiet --batch --yes --passphrase-file "$VAULT_KEY" -d "$VAULT_DIR/$key.gpg" 2>/dev/null) + if [ $? -ne 0 ]; then + echo "$(render_msg "${tr[decrypt_fail]}" "key=$key")" + return 1 + fi + + echo "$value" +} diff --git a/core/utils/vault_utils.tr.en b/core/utils/vault_utils.tr.en new file mode 100644 index 0000000..413952e --- /dev/null +++ b/core/utils/vault_utils.tr.en @@ -0,0 +1,2 @@ +missing=❌ [vault] Secret '{key}' not found in {dir} +decrypt_fail=❌ [vault] Failed to decrypt '{key}' diff --git a/core/utils/vault_utils.tr.es b/core/utils/vault_utils.tr.es new file mode 100644 index 0000000..f81ee15 --- /dev/null +++ b/core/utils/vault_utils.tr.es @@ -0,0 +1,2 @@ +missing=❌ [vault] Secreto '{key}' no encontrado en {dir} +decrypt_fail=❌ [vault] Error al descifrar '{key}' |
