diff --git a/Makefile b/Makefile index 182d088..bc66483 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,10 @@ fmt: lint: bash -O globstar -c 'shellcheck -x bin/mona modules/**/*.sh' shfmt -d bin modules recipes tests + ec test: bats -r tests ci: lint test + diff --git a/README.md b/README.md index 971eb92..76a9a81 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MonaRepo TUI +# MonaRepo TUI (v0.4.0) [![CI](https://github.com/Monynha-Softwares/MonaRepoTui/actions/workflows/lint_test.yml/badge.svg)](https://github.com/Monynha-Softwares/MonaRepoTui/actions/workflows/lint_test.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) @@ -14,10 +14,14 @@ sudo ./bin/mona ``` ## Features +- Installer Wizard for GitHub/Docker + - interactive `.env` helper + - `docker compose` pull/up/ps/logs + - README viewer (`less` fallback, `MONA_USE_TUI_READER=1`) - Bash modules & recipes -- Interactive TUI +- Interactive TUI (bashsimplecurses) - Dry-run mode -- CI ready (shfmt, shellcheck, bats) +- CI ready (shfmt, shellcheck, bats, editorconfig) ## Install @@ -29,6 +33,17 @@ cd MonaRepoTui make install-dev ``` +### TUI dependency + +The interactive menus use [bashsimplecurses](https://github.com/metal3d/bashsimplecurses). +If `ui/bashsimplecurses/simple_curses.sh` is missing, `bin/mona` will try to download it at +runtime (`curl` required). To vendor it instead: + +```bash +git submodule add https://github.com/metal3d/bashsimplecurses ui/bashsimplecurses +git submodule update --init --recursive +``` + ## Quickstart Run the TUI (root recommended): @@ -43,6 +58,25 @@ sudo ./bin/mona ./bin/mona --help ``` +## Installer Wizard + +`./bin/mona` ships an installer that can bootstrap projects from GitHub or Docker images. +It detects common actions and runs them one by one, logging everything to +`~/.mona/logs` (override with `$MONA_LOG_DIR`). + +Examples: + +```bash +# GitHub clone with PAT and README TUI viewer +GITHUB_TOKEN=ghp_xxxx MONA_USE_TUI_READER=1 ./bin/mona + +# Preview commands without executing +MONA_DRY_RUN=true ./bin/mona +``` + +Detected actions include copying `.env.example` to `.env` with an interactive editor, +`docker compose pull/up/ps/logs`, `npm|yarn|pnpm install/build/dev/start`, and more. + ## Development Format and lint: diff --git a/bin/mona b/bin/mona index 31e8b8d..c9ea8b1 100755 --- a/bin/mona +++ b/bin/mona @@ -217,7 +217,7 @@ flow_config_hosts() { apply "hostnamectl set-hostname '$new_host'" # /etc/hosts minimal if [[ -f /etc/hosts ]]; then - apply "cp /etc/hosts /etc/hosts.bak.mona.$(date +%s)" + backup_file /etc/hosts if ! $MONA_DRY_RUN; then awk -v h="$new_host" '!( ($2==h) || ($2 ~ h"\\.") )' /etc/hosts >/etc/hosts.mona || true mv /etc/hosts.mona /etc/hosts || true @@ -280,7 +280,9 @@ YAML echo "-----------------------------------------" read -r -p "Aplicar? [y/N]: " ok || true if [[ "${ok^^}" == Y* ]]; then - apply "cp /etc/netplan/99-mona.yaml /etc/netplan/99-mona.yaml.bak 2>/dev/null || true" + if [[ -f /etc/netplan/99-mona.yaml ]]; then + backup_file /etc/netplan/99-mona.yaml + fi if ! $MONA_DRY_RUN; then printf "%s\n" "$netplan_yaml" >/etc/netplan/99-mona.yaml; fi apply "netplan apply" log "Netplan aplicado." diff --git a/lib/common.sh b/lib/common.sh index f64ac53..5d233d5 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -7,15 +7,33 @@ IFS=$'\n\t' . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/log.sh" needs_root() { - [[ $EUID -eq 0 ]] || { log_error "Execute como root (sudo)."; exit 1; } + [[ $EUID -eq 0 ]] || { + log_error "Execute como root (sudo)." + exit 1 + } } pm_detect() { - if command -v apt-get >/dev/null 2>&1; then echo apt; return; fi - if command -v dnf >/dev/null 2>&1; then echo dnf; return; fi - if command -v yum >/dev/null 2>&1; then echo yum; return; fi - if command -v pacman >/dev/null 2>&1; then echo pacman; return; fi - if command -v zypper >/dev/null 2>&1; then echo zypper; return; fi + if command -v apt-get >/dev/null 2>&1; then + echo apt + return + fi + if command -v dnf >/dev/null 2>&1; then + echo dnf + return + fi + if command -v yum >/dev/null 2>&1; then + echo yum + return + fi + if command -v pacman >/dev/null 2>&1; then + echo pacman + return + fi + if command -v zypper >/dev/null 2>&1; then + echo zypper + return + fi echo unknown } @@ -25,12 +43,15 @@ pkg_install() { log_info "Instalando pacotes via $pm: ${pkgs[*]}" [[ "${MONA_DRY_RUN:-false}" == "true" ]] && return 0 case "$pm" in - apt) apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}" ;; - dnf) dnf install -y "${pkgs[@]}" ;; - yum) yum install -y "${pkgs[@]}" ;; - pacman) pacman -Sy --noconfirm "${pkgs[@]}" ;; - zypper) zypper --non-interactive install --no-recommends "${pkgs[@]}" ;; - *) log_error "Gerenciador de pacotes não suportado"; return 1 ;; + apt) apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}" ;; + dnf) dnf install -y "${pkgs[@]}" ;; + yum) yum install -y "${pkgs[@]}" ;; + pacman) pacman -Sy --noconfirm "${pkgs[@]}" ;; + zypper) zypper --non-interactive install --no-recommends "${pkgs[@]}" ;; + *) + log_error "Gerenciador de pacotes não suportado" + return 1 + ;; esac } @@ -42,4 +63,10 @@ apply() { fi } +backup_file() { + local src="$1" dst="${1}.bak.mona.$(date +%s)" + log "[mona] backup: $src -> $dst" + apply "cp '$src' '$dst'" +} + pause_any() { read -r -p $'Pressione Enter para voltar ao menu…\n' _ || true; } diff --git a/tests/backup.bats b/tests/backup.bats new file mode 100644 index 0000000..003896a --- /dev/null +++ b/tests/backup.bats @@ -0,0 +1,15 @@ +#!/usr/bin/env bats + +setup() { + cd "$BATS_TEST_DIRNAME/.." +} + +@test "backup_file creates timestamped backup" { + tmp=$(mktemp) + echo foo >"$tmp" + run bash -lc "MONA_DIR=$(pwd); . lib/common.sh; backup_file '$tmp'" + [ "$status" -eq 0 ] + [[ "$output" == *"[mona] backup:"* ]] + backup=$(ls "$tmp.bak.mona."*) + [ -f "$backup" ] +} diff --git a/tests/cli.bats b/tests/cli.bats index 565aa9e..a6b7c2d 100644 --- a/tests/cli.bats +++ b/tests/cli.bats @@ -15,3 +15,8 @@ setup() { [ "$status" -eq 0 ] [[ "$output" == *"Usage:"* ]] } + +@test "bin/mona --dry-run exits 0" { + run env MONA_NONINTERACTIVE=1 ./bin/mona --dry-run <<<"" + [ "$status" -eq 0 ] +}