diff --git a/AGENTS.md b/AGENTS.md index 7f3fce4..d7fa623 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,9 +29,14 @@ Extend MonaRepo by adding **modules** (idempotent bash units) and **recipes** (o - Use `trap` for cleanup if creating temp files - Prefer POSIX sh where feasible; when not, document Bashisms. - ## New Track: Project Installer - Enhance GitHub listing (paginate, filter by forks/archived, support PAT if available). - Teach the wizard to parse `README.md` for “Quick Start” sections and propose actions accordingly. - Add TUI page viewer for README (fallback to `less`). - Extend docker path with compose files and .env handling. + +### Installer roadmap (next tasks) +- Parse `mona.yaml` playbooks (YAML subset) to define guided actions (title, command, confirm, rollback). +- Add `.env` schema validator (required keys, default values). +- Enhance README parser to auto-propose actions from “Quick Start” sections. +- Add multi-host inventory integration to run the same installer on several servers via SSH. diff --git a/README.md b/README.md index 8b9a0dc..099a76f 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,6 @@ MIT — see [LICENSE](LICENSE). - `recipes/monitoring-node.sh` — base + docker + node_exporter + cAdvisor - `recipes/supabase-node.sh` — base + docker (hook para Supabase) - ## Project Installer (Wizard) Use the TUI option **“Instalar/Configurar Projeto (GitHub/Docker)”** to: - List or select a GitHub repo (Monynha-Softwares by default) and clone it @@ -71,3 +70,9 @@ Use the TUI option **“Instalar/Configurar Projeto (GitHub/Docker)”** to: - Then MonaRepo detects common post‑clone actions (e.g., copy `.env.example`, `docker compose up -d`, `npm/pnpm/yarn install`, `make setup`, `supabase start`) and lets you run them step‑by‑step while logging output. Logs are stored in `~/.mona/logs` (or `$MONA_LOG_DIR`). `--dry-run` prints commands without executing. + +### Installer extras (v0.4.0) +- **GitHub PAT**: set `GITHUB_TOKEN` to avoid rate limits and access private repos. +- **Interactive `.env`**: when a project has `.env.example`, choose “Criar .env interativo” to fill values key-by-key. +- **Docker Compose advanced**: auto-detects `docker compose` (plugin) or `docker-compose`; offers `pull`, `up -d --remove-orphans`, `ps`, `logs -f`. +- **TUI README viewer** (experimental): set `MONA_USE_TUI_READER=1` to view README inside the TUI; otherwise falls back to `less`. diff --git a/bin/mona b/bin/mona index cd4163e..991f190 100644 --- a/bin/mona +++ b/bin/mona @@ -5,7 +5,7 @@ set -euo pipefail # shellcheck source=../lib/common.sh . "${MONA_DIR}/lib/common.sh" -VERSION="0.3.0" +VERSION="0.4.0" # ------------------------------ Globals ------------------------------------ export MONA_BIN_PATH="$(readlink -f "$0")" diff --git a/modules/installer/github.sh b/modules/installer/github.sh index 5b26621..db6f903 100644 --- a/modules/installer/github.sh +++ b/modules/installer/github.sh @@ -13,9 +13,9 @@ gh_list_org_repos(){ return 1 fi # Nota: sem jq; parse simples de nomes. - curl -fsSL "$GITHUB_API/orgs/$org/repos?per_page=$per_page" 2>/dev/null | \ - grep -E '^\s*"name":\s*"' | sed -E 's/^\s*"name":\s*"([^"]+)".*/\1/' | sort -f -u -} + local hdr=() + [[ -n "${GITHUB_TOKEN:-}" ]] && hdr+=(-H "Authorization: Bearer $GITHUB_TOKEN") + curl -fsSL "${hdr[@]}" "$GITHUB_API/orgs/$org/repos?per_page=$per_page" 2>/dev/null | \ gh_clone(){ local repo_url="$1" dest_dir="$2" branch="${3:-}" diff --git a/modules/installer/wizard.sh b/modules/installer/wizard.sh index 8ced6ca..f56a600 100644 --- a/modules/installer/wizard.sh +++ b/modules/installer/wizard.sh @@ -14,6 +14,105 @@ ensure_projects_dir(){ apply "mkdir -p '$PROJECTS_BASE'" } +# Try to source simple_curses if TUI reader enabled +maybe_source_curses(){ + local sc="${MONA_DIR:-}/ui/bashsimplecurses/simple_curses.sh" + if [[ "${MONA_USE_TUI_READER:-0}" == "1" && -f "$sc" ]]; then + # shellcheck disable=SC1090 + source "$sc" + return 0 + fi + return 1 +} + +tui_view_file(){ + local file="$1" + [[ -f "$file" ]] || { warn "Arquivo não encontrado: $file"; return 1; } + if ! maybe_source_curses; then + if command -v less >/dev/null 2>&1; then less -R "$file"; else cat "$file"; fi + pause_any + return 0 + fi + local -a lines + mapfile -t lines <"$file" + local top=0 + local max=${#lines[@]} + local page=20 + + _draw(){ + window "README (TUI) — ↑/↓ rola, Q sai" "blue" "100%" + local i; local end=$(( top + page )) + (( end > max )) && end=$max + for (( i=top; i0 )) && ((top--));; + $'\e[B') (( top+page/dev/null 2>&1; then + if docker compose version >/dev/null 2>&1; then + echo "docker compose" + return 0 + fi + if command -v docker-compose >/dev/null 2>&1; then + echo "docker-compose" + return 0 + fi + fi + echo "" + return 1 +} + +env_interactive_from_example(){ + local dir="$1" + local src="$dir/.env.example" + local dst="$dir/.env" + [[ -f "$src" ]] || { err ".env.example não encontrado"; return 1; } + echo "Gerando $dst a partir de $src (interativo). ENTER mantém o default, espaço em branco mantém vazio." + [[ -f "$dst" ]] && cp "$dst" "$dst.bak.mona.$(date +%s)" + : > "$dst" + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^\s*# ]] || [[ -z "$line" ]]; then + echo "$line" >> "$dst" + continue + fi + # parse KEY=VALUE (tira aspas simples/duplas do default) + key="${line%%=*}"; def="${line#*=}" + def="${def%$'\r'}" + def="${def%\"}"; def="${def#\"}"; def="${def%\'}"; def="${def#\'}" + read -r -p "$key [${def}]: " val + if [[ -z "${val}" ]]; then + val="${def}" + fi + printf "%s=%s\n" "$key" "$val" >> "$dst" + done < "$src" + log ".env gerado: $dst" +} + + # discover actions in a project checkout discover_actions(){ local dir="$1" @@ -23,6 +122,21 @@ discover_actions(){ if [[ -f "$dir/.env.example" ]]; then ACTIONS+=("Copiar .env.example → .env") ACTION_CMDS+=("cp -n '$dir/.env.example' '$dir/.env'") + ACTIONS+=("Criar .env interativo (de .env.example)") + ACTION_CMDS+=("env_interactive_from_example '$dir'") + fi + + local ccmd + ccmd=$(compose_cmd_for "$dir") || true + if [[ -n "$ccmd" ]] && [[ -f "$dir/docker-compose.yml" || -f "$dir/docker-compose.yaml" ]]; then + ACTIONS+=("$ccmd pull") + ACTION_CMDS+=("cd '$dir' && $ccmd pull") + ACTIONS+=("$ccmd up -d --remove-orphans") + ACTION_CMDS+=("cd '$dir' && $ccmd up -d --remove-orphans") + ACTIONS+=("$ccmd ps") + ACTION_CMDS+=("cd '$dir' && $ccmd ps") + ACTIONS+=("$ccmd logs -f (Ctrl+C para sair)") + ACTION_CMDS+=("cd '$dir' && $ccmd logs -f") fi if [[ -f "$dir/docker-compose.yml" || -f "$dir/docker-compose.yaml" ]]; then @@ -83,6 +197,7 @@ run_actions_menu(){ local dir="$1" discover_actions "$dir" + if [[ "${MONA_USE_TUI_READER:-0}" == "1" ]]; then tui_view_file "$dir/README.md" || true; else show_readme "$dir" || true; fi show_readme "$dir" || true if ((${#ACTIONS[@]}==0)); then diff --git a/tests/installer_env.bats b/tests/installer_env.bats new file mode 100644 index 0000000..430375a --- /dev/null +++ b/tests/installer_env.bats @@ -0,0 +1,9 @@ +#!/usr/bin/env bats + +setup() { cd "$BATS_TEST_DIRNAME/.."; } + +@test "wizard provides env_interactive_from_example function" { + run bash -lc 'source modules/installer/wizard.sh; type -t env_interactive_from_example' + [ "$status" -eq 0 ] + [[ "$output" == "function" ]] +}