Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,16 @@ 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
- Or pull/run a Docker image quickly
- 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`.
2 changes: 1 addition & 1 deletion bin/mona
Original file line number Diff line number Diff line change
Expand Up @@ -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")"
Expand Down
6 changes: 3 additions & 3 deletions modules/installer/github.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}"
Expand Down
115 changes: 115 additions & 0 deletions modules/installer/wizard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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; i<end; i++ )); do
append "${lines[$i]}"
done
addsep
append "Linhas: $((top+1))–$end de $max"
endwin
}

main(){ _draw; }
readKey(){ local k; IFS= read -rsn1 -t 0.2 k || true
if [[ "$k" == $'\e' ]]; then
local k2 k3; IFS= read -rsn1 -t 0.001 k2 || true; IFS= read -rsn1 -t 0.001 k3 || true
k+="$k2$k3"
fi
printf '%s' "$k"
}
update(){
local key; key=$(readKey)
case "$key" in
$'\e[A') (( top>0 )) && ((top--));;
$'\e[B') (( top+page<max )) && ((top++));;
q|Q) return 1;;
esac
}
main_loop -t 1 || true
}

compose_cmd_for(){
local dir="$1"
if command -v docker >/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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions tests/installer_env.bats
Original file line number Diff line number Diff line change
@@ -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" ]]
}
Loading