#!/bin/bash set -euo pipefail # ================================================================ # Guacamole Management Script # Upravljanje instalacijom nakon postavljanja # ================================================================ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${GREEN}[✓]${NC} $*"; } warn() { echo -e "${YELLOW}[⚠]${NC} $*"; } error() { echo -e "${RED}[✗]${NC} $*" >&2; } section() { echo -e "\n${BOLD}${BLUE}══════════ $* ══════════${NC}"; } ok() { echo -e "${GREEN}$*${NC}"; } [[ $EUID -ne 0 ]] && { echo -e "${RED}Pokrenite kao root: sudo bash $0${NC}"; exit 1; } # --------------------------------------------------------------- # Detekcija instalacije # --------------------------------------------------------------- INSTALL_DIR="/opt/guacamole" GUAC_PORT="8080" detect_nginx_conf() { if [[ -f /etc/nginx/sites-available/guacamole ]]; then echo "/etc/nginx/sites-available/guacamole" elif [[ -f /etc/nginx/conf.d/guacamole.conf ]]; then echo "/etc/nginx/conf.d/guacamole.conf" else echo "" fi } detect_compose_cmd() { if docker compose version &>/dev/null 2>&1; then echo "docker compose" elif command -v docker-compose &>/dev/null; then echo "docker-compose" else echo "" fi } NGINX_CONF=$(detect_nginx_conf) COMPOSE_CMD=$(detect_compose_cmd) [[ -z "$COMPOSE_CMD" ]] && { error "Docker Compose nije pronađen"; exit 1; } # --------------------------------------------------------------- # Pomoćne funkcije # --------------------------------------------------------------- nginx_reload() { nginx -t && systemctl reload nginx info "Nginx reloadiran" } get_domain() { if [[ -n "$NGINX_CONF" ]]; then grep -m1 "server_name" "$NGINX_CONF" | awk '{print $2}' | tr -d ';' else hostname -I | awk '{print $1}' fi } pause() { echo "" read -rp "Pritisnite Enter za povratak u meni..." } confirm() { read -rp "$(echo -e "${YELLOW}$* [y/N]: ${NC}")" ans [[ "$ans" =~ ^[Yy]$ ]] } # --------------------------------------------------------------- # Status pregled # --------------------------------------------------------------- show_status() { section "Status servisa" echo -e "\n${BOLD}Docker kontejneri:${NC}" for c in guacamole guacd guac_postgres; do if docker ps --filter "name=^/${c}$" --filter "status=running" | grep -q "$c" 2>/dev/null; then echo -e " ${GREEN}●${NC} $c — aktivan" else echo -e " ${RED}●${NC} $c — NIJE aktivan" fi done echo -e "\n${BOLD}Sistemski servisi:${NC}" for svc in nginx fail2ban docker; do if systemctl is-active --quiet "$svc" 2>/dev/null; then echo -e " ${GREEN}●${NC} $svc — aktivan" else echo -e " ${RED}●${NC} $svc — NIJE aktivan" fi done echo -e "\n${BOLD}SSL certifikat:${NC}" local domain domain=$(get_domain) if certbot certificates 2>/dev/null | grep -q "$domain"; then local expiry expiry=$(certbot certificates 2>/dev/null | grep "Expiry Date" | head -1 | awk '{print $3, $4}') echo -e " ${GREEN}●${NC} Aktivan — ističe: $expiry" else echo -e " ${YELLOW}●${NC} SSL nije postavljen" fi echo -e "\n${BOLD}fail2ban — zabanjeni IP-ovi:${NC}" if systemctl is-active --quiet fail2ban 2>/dev/null; then local banned banned=$(fail2ban-client status nginx-basicauth 2>/dev/null | grep "Banned IP" | cut -d: -f2 | xargs) if [[ -n "$banned" && "$banned" != " " ]]; then echo -e " ${RED}$banned${NC}" else echo -e " ${GREEN}Nema zabanjenih IP-ova${NC}" fi else echo -e " ${YELLOW}fail2ban nije aktivan${NC}" fi echo -e "\n${BOLD}Nginx konfiguracija:${NC} ${NGINX_CONF:-nije pronađena}" echo -e "${BOLD}Instalacijski dir:${NC} ${INSTALL_DIR}" echo -e "${BOLD}Domena:${NC} $(get_domain)" pause } # --------------------------------------------------------------- # Basic Auth upravljanje # --------------------------------------------------------------- menu_basicauth() { while true; do clear section "Basic Auth upravljanje" echo "" echo " 1) Promijeni lozinku postojećeg korisnika" echo " 2) Dodaj novog korisnika" echo " 3) Ukloni korisnika" echo " 4) Prikaži korisnike" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) echo "" read -rp "Korisničko ime: " user read -rsp "Nova lozinka: " pass; echo if htpasswd -b /etc/nginx/.guac_htpasswd "$user" "$pass" 2>/dev/null; then nginx_reload info "Lozinka promijenjena za: $user" else error "Korisnik '$user' ne postoji. Dodaj ga opcijom 2." fi pause ;; 2) echo "" read -rp "Novo korisničko ime: " user read -rsp "Lozinka: " pass; echo htpasswd -b /etc/nginx/.guac_htpasswd "$user" "$pass" nginx_reload info "Korisnik dodan: $user" pause ;; 3) echo "" read -rp "Korisničko ime za uklanjanje: " user if confirm "Sigurno želite ukloniti korisnika '$user'?"; then htpasswd -D /etc/nginx/.guac_htpasswd "$user" nginx_reload info "Korisnik uklonjen: $user" fi pause ;; 4) echo "" echo -e "${BOLD}Korisnici u /etc/nginx/.guac_htpasswd:${NC}" cut -d: -f1 /etc/nginx/.guac_htpasswd 2>/dev/null | while read -r u; do echo " • $u" done pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } # --------------------------------------------------------------- # SSL upravljanje # --------------------------------------------------------------- menu_ssl() { while true; do clear section "SSL upravljanje" echo "" echo " 1) Omogući SSL (Let's Encrypt)" echo " 2) Obnovi certifikat ručno" echo " 3) Prikaži informacije o certifikatu" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) echo "" local domain domain=$(get_domain) read -rp "Domena [${domain}]: " input_domain domain="${input_domain:-$domain}" read -rp "Email za Let's Encrypt: " email [[ -z "$email" ]] && { warn "Email je obavezan"; pause; continue; } if ! command -v certbot &>/dev/null; then warn "Certbot nije instaliran. Instaliram..." if command -v apt-get &>/dev/null; then apt-get install -y certbot python3-certbot-nginx elif command -v dnf &>/dev/null; then dnf install -y certbot python3-certbot-nginx elif command -v yum &>/dev/null; then yum install -y certbot python3-certbot-nginx else error "Instalirajte certbot ručno" pause; continue fi fi certbot --nginx -d "$domain" --email "$email" \ --agree-tos --non-interactive --redirect # Auto-obnova if systemctl list-unit-files certbot.timer &>/dev/null; then systemctl enable --now certbot.timer else grep -q "certbot renew" /etc/crontab 2>/dev/null || \ echo "0 3 * * * root certbot renew --quiet --post-hook 'systemctl reload nginx'" \ >> /etc/crontab fi nginx_reload info "SSL aktivan za: $domain" pause ;; 2) certbot renew --force-renewal systemctl reload nginx info "Certifikat obnovljen" pause ;; 3) certbot certificates 2>/dev/null || warn "Nema instaliranih certifikata" pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } # --------------------------------------------------------------- # GeoIP upravljanje # --------------------------------------------------------------- menu_geoip() { while true; do clear section "GeoIP upravljanje" echo "" echo " 1) Omogući GeoIP blokiranje" echo " 2) Promijeni dozvoljene zemlje" echo " 3) Onemogući GeoIP blokiranje" echo " 4) Ažuriraj GeoIP bazu" echo " 5) Prikaži trenutne postavke" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) echo "" if [[ -f /etc/nginx/conf.d/guacamole-geoip.conf ]]; then warn "GeoIP je već konfiguriran. Koristi opciju 2 za promjenu zemalja." pause; continue fi warn "Potreban besplatni MaxMind račun: https://www.maxmind.com/en/geolite2/signup" read -rp "MaxMind Account ID: " mm_id read -rp "MaxMind License Key: " mm_key read -rp "Dozvoljene zemlje (ISO kodovi, razmak, npr: HR BA SI): " countries # Instalacija ovisno o distro if command -v apt-get &>/dev/null; then apt-get install -y libnginx-mod-http-geoip2 geoipupdate 2>/dev/null || \ warn "Neki paketi nisu instalirani — provjeri ručno" elif command -v dnf &>/dev/null; then dnf install -y nginx-mod-http-geoip2 geoipupdate 2>/dev/null || \ warn "Neki paketi nisu dostupni — provjeri ručno" fi # MaxMind konfiguracija mkdir -p /usr/share/GeoIP cat > /etc/GeoIP.conf << MMEOF AccountID ${mm_id} LicenseKey ${mm_key} EditionIDs GeoLite2-Country DatabaseDirectory /usr/share/GeoIP MMEOF geoipupdate && info "GeoIP baza preuzeta" || \ { error "geoipupdate nije uspio — provjeri kredencijale"; pause; continue; } _write_geoip_conf "$countries" _apply_geoip_to_nginx nginx_reload info "GeoIP blokiranje aktivno — dozvoljene: $countries" pause ;; 2) echo "" if [[ ! -f /etc/nginx/conf.d/guacamole-geoip.conf ]]; then warn "GeoIP nije omogućen. Odaberi opciju 1." pause; continue fi echo -e "Trenutne dozvoljene zemlje:" grep -E "^\s+[A-Z]{2}\s+1;" /etc/nginx/conf.d/guacamole-geoip.conf \ | awk '{print " "$1}' | tr '\n' ' ' echo "" read -rp "Nove dozvoljene zemlje (ISO kodovi, razmak): " countries _write_geoip_conf "$countries" nginx_reload info "Ažurirane dozvoljene države: $countries" pause ;; 3) echo "" if confirm "Sigurno želite onemogućiti GeoIP blokiranje?"; then rm -f /etc/nginx/conf.d/guacamole-geoip.conf # Ukloni GeoIP provjeru iz nginx location bloka if [[ -n "$NGINX_CONF" ]]; then sed -i '/allowed_country/d' "$NGINX_CONF" fi nginx_reload info "GeoIP blokiranje onemogućeno" fi pause ;; 4) if command -v geoipupdate &>/dev/null; then geoipupdate && info "GeoIP baza ažurirana" || error "Ažuriranje nije uspjelo" else error "geoipupdate nije instaliran" fi pause ;; 5) echo "" if [[ -f /etc/nginx/conf.d/guacamole-geoip.conf ]]; then echo -e "${BOLD}GeoIP status:${NC} ${GREEN}Aktivan${NC}" echo -e "${BOLD}Dozvoljene zemlje:${NC}" grep -E "^\s+[A-Z]{2}\s+1;" /etc/nginx/conf.d/guacamole-geoip.conf \ | awk '{print " "$1}' | tr '\n' ' ' echo "" if [[ -f /usr/share/GeoIP/GeoLite2-Country.mmdb ]]; then echo -e "${BOLD}GeoIP baza:${NC} $(stat -c '%y' /usr/share/GeoIP/GeoLite2-Country.mmdb | cut -d. -f1)" fi else echo -e "${BOLD}GeoIP status:${NC} ${YELLOW}Nije aktivan${NC}" fi pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } _write_geoip_conf() { local countries="$1" local entries="" for cc in $countries; do entries+=" ${cc} 1;\n" done cat > /etc/nginx/conf.d/guacamole-geoip.conf << EOF geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { \$geoip2_data_country_code country iso_code; } map \$geoip2_data_country_code \$allowed_country { default 0; $(echo -e "$entries")} EOF } _apply_geoip_to_nginx() { if [[ -n "$NGINX_CONF" ]]; then # Ukloni staru provjeru ako postoji pa dodaj novu sed -i '/allowed_country/d' "$NGINX_CONF" sed -i '/auth_basic.*Restricted/i\ if ($allowed_country = 0) { return 403; }' "$NGINX_CONF" fi } # --------------------------------------------------------------- # fail2ban upravljanje # --------------------------------------------------------------- menu_fail2ban() { while true; do clear section "fail2ban upravljanje" echo "" echo " 1) Prikaži zabanjene IP-ove" echo " 2) Odbani IP adresu" echo " 3) Ručno banaj IP adresu" echo " 4) Promijeni broj pokušaja prije bana" echo " 5) Promijeni trajanje bana" echo " 6) Prikaži fail2ban log" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) echo "" for jail in nginx-basicauth nginx-guacamole sshd; do echo -e "${BOLD}[$jail]${NC}" fail2ban-client status "$jail" 2>/dev/null \ | grep -E "Currently banned|Banned IP" || echo " jail nije aktivan" echo "" done pause ;; 2) echo "" read -rp "IP adresa za odbanjivanje: " ip for jail in nginx-basicauth nginx-guacamole sshd; do fail2ban-client set "$jail" unbanip "$ip" 2>/dev/null && \ info "$ip odbanjivan iz $jail" || true done pause ;; 3) echo "" read -rp "IP adresa za baniranje: " ip read -rp "Jail [nginx-basicauth]: " jail jail="${jail:-nginx-basicauth}" fail2ban-client set "$jail" banip "$ip" info "$ip baniran u $jail" pause ;; 4) echo "" local conf="/etc/fail2ban/jail.d/guacamole.conf" current=$(grep -m1 "maxretry" "$conf" 2>/dev/null | awk '{print $3}') read -rp "Broj pokušaja prije bana [trenutno: ${current:-5}]: " retries retries="${retries:-${current:-5}}" sed -i "s/^maxretry.*/maxretry = $retries/" "$conf" systemctl restart fail2ban info "maxretry postavljeno na: $retries" pause ;; 5) echo "" local conf="/etc/fail2ban/jail.d/guacamole.conf" current=$(grep -m1 "bantime" "$conf" 2>/dev/null | awk '{print $3}') echo "Unesite trajanje u sekundama (3600=1h, 86400=1dan, 604800=1tjedan)" read -rp "Trajanje bana [trenutno: ${current:-3600}s]: " bantime bantime="${bantime:-${current:-3600}}" sed -i "s/^bantime.*/bantime = $bantime/" "$conf" systemctl restart fail2ban info "bantime postavljeno na: ${bantime}s" pause ;; 6) echo "" journalctl -u fail2ban -n 50 --no-pager | tail -50 pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } # --------------------------------------------------------------- # Guacamole upravljanje # --------------------------------------------------------------- menu_guacamole() { while true; do clear section "Guacamole upravljanje" echo "" echo " 1) Prikaži logove (live)" echo " 2) Restart svih kontejnera" echo " 3) Stop svih kontejnera" echo " 4) Start svih kontejnera" echo " 5) Ažuriraj Guacamole (pull nove image-e)" echo " 6) Prikaži korištenje resursa (CPU/RAM)" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) echo -e "${YELLOW}Ctrl+C za izlaz${NC}" cd "$INSTALL_DIR" && $COMPOSE_CMD logs -f --tail=100 ;; 2) cd "$INSTALL_DIR" && $COMPOSE_CMD restart info "Kontejneri restartani" pause ;; 3) if confirm "Sigurno želite zaustaviti Guacamole?"; then cd "$INSTALL_DIR" && $COMPOSE_CMD stop info "Kontejneri zaustavljeni" fi pause ;; 4) cd "$INSTALL_DIR" && $COMPOSE_CMD start info "Kontejneri pokrenuti" pause ;; 5) echo "" warn "Ovo će preuzeti najnovije Docker image-e i restartati servise." if confirm "Nastaviti?"; then cd "$INSTALL_DIR" $COMPOSE_CMD pull $COMPOSE_CMD up -d --force-recreate info "Guacamole ažuriran" fi pause ;; 6) docker stats guacamole guacd guac_postgres --no-stream pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } # --------------------------------------------------------------- # Nginx upravljanje # --------------------------------------------------------------- menu_nginx() { while true; do clear section "Nginx upravljanje" echo "" echo " 1) Reload konfiguracije" echo " 2) Test konfiguracije" echo " 3) Prikaži nginx access log (live)" echo " 4) Promijeni rate limit (zahtjevi/minuti)" echo " 5) Prikaži aktivne konekcije" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) nginx_reload pause ;; 2) nginx -t pause ;; 3) echo -e "${YELLOW}Ctrl+C za izlaz${NC}" tail -f /var/log/nginx/access.log ;; 4) echo "" local zones_conf="/etc/nginx/conf.d/guacamole-maps.conf" current=$(grep "guac_req" "$zones_conf" 2>/dev/null | grep -oP '\d+(?=r/m)' | head -1) read -rp "Rate limit zahtjevi/minuti [trenutno: ${current:-10}]: " rate rate="${rate:-${current:-10}}" sed -i "s/rate=[0-9]*r\/m rate=.*guac_req/rate=${rate}r\/m/" "$zones_conf" 2>/dev/null || \ sed -i "s/guac_req:10m rate=[0-9]*r\/m/guac_req:10m rate=${rate}r\/m/" "$zones_conf" nginx_reload info "Rate limit postavljen na: ${rate} req/min" pause ;; 5) echo "" ss -tnp | grep nginx || echo "Nema aktivnih nginx konekcija" pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } # --------------------------------------------------------------- # Backup & restore # --------------------------------------------------------------- menu_backup() { while true; do clear section "Backup & Restore" echo "" echo " 1) Napravi backup (konfiguracija + baza)" echo " 2) Prikaži dostupne backupe" echo " 3) Vrati backup" echo " 0) Povratak" echo "" read -rp "Odabir: " choice case "$choice" in 1) local ts backup_file ts=$(date +%Y%m%d_%H%M%S) backup_file="/root/guacamole_backup_${ts}.tar.gz" echo "" info "Backupiranje baze podataka..." docker exec guac_postgres pg_dump -U guacamole_user guacamole_db \ > /tmp/guacamole_db_backup.sql info "Arhiviranje konfiguracije..." tar -czf "$backup_file" \ -C / \ --ignore-failed-read \ opt/guacamole \ etc/nginx/.guac_htpasswd \ etc/nginx/conf.d/guacamole-maps.conf \ etc/nginx/conf.d/guacamole-geoip.conf \ etc/nginx/sites-available/guacamole \ etc/nginx/conf.d/guacamole.conf \ etc/fail2ban/jail.d/guacamole.conf \ etc/fail2ban/filter.d/nginx-basicauth.conf \ etc/fail2ban/filter.d/nginx-guacamole.conf \ tmp/guacamole_db_backup.sql \ 2>/dev/null || true rm -f /tmp/guacamole_db_backup.sql chmod 600 "$backup_file" info "Backup kreiran: $backup_file" echo -e "Veličina: $(du -sh "$backup_file" | cut -f1)" pause ;; 2) echo "" if ls /root/guacamole_backup_*.tar.gz &>/dev/null; then ls -lh /root/guacamole_backup_*.tar.gz else warn "Nema dostupnih backupa u /root/" fi pause ;; 3) echo "" if ! ls /root/guacamole_backup_*.tar.gz &>/dev/null; then warn "Nema dostupnih backupa" pause; continue fi ls -1 /root/guacamole_backup_*.tar.gz | nl read -rp "Broj backupa za vraćanje: " num local backup backup=$(ls -1 /root/guacamole_backup_*.tar.gz | sed -n "${num}p") [[ -z "$backup" ]] && { warn "Nevažeći odabir"; pause; continue; } if confirm "Vraćanje: $backup — ovo će prepisati trenutne postavke. Nastaviti?"; then tar -xzf "$backup" -C / # Restore baze if tar -tzf "$backup" | grep -q "guacamole_db_backup.sql"; then docker exec -i guac_postgres psql -U guacamole_user guacamole_db \ < /tmp/guacamole_db_backup.sql rm -f /tmp/guacamole_db_backup.sql fi nginx_reload systemctl restart fail2ban cd "$INSTALL_DIR" && $COMPOSE_CMD restart info "Backup vraćen: $backup" fi pause ;; 0) return ;; *) warn "Nevažeći odabir" ;; esac done } # --------------------------------------------------------------- # Glavni meni # --------------------------------------------------------------- main_menu() { while true; do clear echo -e "${BOLD}${BLUE}" echo " ╔═══════════════════════════════════════╗" echo " ║ Guacamole Management Console ║" echo " ╚═══════════════════════════════════════╝${NC}" echo "" echo -e " ${BOLD}1)${NC} Status servisa" echo -e " ${BOLD}2)${NC} Basic Auth (korisnici)" echo -e " ${BOLD}3)${NC} SSL certifikat" echo -e " ${BOLD}4)${NC} GeoIP blokiranje" echo -e " ${BOLD}5)${NC} fail2ban (banned IP-ovi)" echo -e " ${BOLD}6)${NC} Guacamole (Docker kontejneri)" echo -e " ${BOLD}7)${NC} Nginx" echo -e " ${BOLD}8)${NC} Backup & Restore" echo "" echo -e " ${BOLD}0)${NC} Izlaz" echo "" read -rp "Odabir: " choice case "$choice" in 1) show_status ;; 2) menu_basicauth ;; 3) menu_ssl ;; 4) menu_geoip ;; 5) menu_fail2ban ;; 6) menu_guacamole ;; 7) menu_nginx ;; 8) menu_backup ;; 0) echo ""; exit 0 ;; *) warn "Nevažeći odabir" ;; esac done } main_menu