Files
GuacPanel/install.sh
T
njomac 149ea6f8f3 refactor: install.sh deploya webgui/ umjesto inline kopije
install.sh je nosio vlastitu kopiju Web GUI-ja u heredoc blokovima i ta
se kopija razisla s webgui/ direktorijem: inline verzija imala je 8
stranica, webgui/ ih ima 10. Stranice Backup i Azuriranja, koje README
i UPUTE.txt dokumentiraju, tako nikad nisu zavrsile na serveru.

Sada se app.py i predlosci kopiraju iz webgui/ pokraj skripte (SRC_DIR).
Uklonjeno ~1090 linija duplikata. Ako webgui/ nedostaje, skripta pukne s
jasnom porukom umjesto da instalira nepotpun GUI.

README i UPUTE dopunjeni: treba kopirati cijeli repo, ne samo install.sh.

Provjereno cistim installom na Ubuntu 24.04: svih 9 stranica vraca 200,
backup stvarno kreira arhivu s pg_dumpom i nginx/ssl konfiguracijom,
TOTP se ucitava, sve prezivi reboot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:36:00 +02:00

784 lines
29 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# ================================================================
# Apache Guacamole + Nginx + Web GUI — All-in-One Installer
# Podržano: Ubuntu/Debian, RHEL/CentOS/Rocky/Alma, Fedora,
# Arch/Manjaro, openSUSE
# ================================================================
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; exit 1; }
section() { echo -e "\n${BOLD}${BLUE}══════════ $* ══════════${NC}"; }
[[ $EUID -ne 0 ]] && error "Pokrenite kao root: sudo bash $0"
# ── Konfigurabilne varijable ──────────────────────────────────────────────────
GUAC_VERSION="1.5.5"
GUAC_PORT="8080"
INSTALL_DIR="/opt/guacamole"
# Direktorij repoa — Web GUI se kopira iz webgui/ pokraj ove skripte.
SRC_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# ── OS detekcija ──────────────────────────────────────────────────────────────
section "Detekcija operativnog sustava"
[[ -f /etc/os-release ]] || error "/etc/os-release nije pronađen — nepodržan OS"
source /etc/os-release
OS_ID="${ID:-unknown}"
OS_VERSION_ID="${VERSION_ID:-0}"
OS_LIKE="${ID_LIKE:-}"
determine_pkg_manager() {
local id="$1" like="$2" ver="$3"
if [[ "$id" =~ ^(ubuntu|debian|linuxmint|pop|kali|raspbian)$ ]] \
|| echo "$like" | grep -qi debian; then
echo "apt"
elif [[ "$id" == "fedora" ]]; then
echo "dnf"
elif [[ "$id" =~ ^(centos|rhel|rocky|almalinux|ol)$ ]] \
|| echo "$like" | grep -qiE "rhel|fedora|centos"; then
if (( ${ver%%.*} >= 8 )); then echo "dnf"; else echo "yum"; fi
elif [[ "$id" =~ ^(arch|manjaro|endeavouros|garuda)$ ]]; then
echo "pacman"
elif [[ "$id" =~ ^(opensuse|sles|opensuse-leap|opensuse-tumbleweed)$ ]] \
|| echo "$like" | grep -qi suse; then
echo "zypper"
else
echo "unknown"
fi
}
PKG=$(determine_pkg_manager "$OS_ID" "$OS_LIKE" "$OS_VERSION_ID")
[[ "$PKG" == "unknown" ]] && error "Nepodržani OS: $OS_ID"
info "OS: ${PRETTY_NAME:-$OS_ID} | Package manager: $PKG"
pkg_update() {
case "$PKG" in
apt) apt-get update -y ;;
dnf) dnf check-update -y || true ;;
yum) yum check-update -y || true ;;
pacman) pacman -Sy --noconfirm ;;
zypper) zypper refresh ;;
esac
}
pkg_install() {
case "$PKG" in
apt) apt-get install -y "$@" ;;
dnf) dnf install -y "$@" ;;
yum) yum install -y "$@" ;;
pacman) pacman -S --noconfirm "$@" ;;
zypper) zypper install -yn "$@" ;;
esac
}
# ── Korisnički unos ───────────────────────────────────────────────────────────
section "Konfiguracija instalacije"
echo ""
read -rp "$(echo -e "${CYAN}Domena${NC} (npr. rdp.firma.com) ili Enter za IP: ")" DOMAIN
DOMAIN="${DOMAIN:-$(hostname -I | awk '{print $1}')}"
read -rp "$(echo -e "${CYAN}Nginx Basic Auth${NC} korisničko ime [admin]: ")" BASIC_USER
BASIC_USER="${BASIC_USER:-admin}"
while true; do
read -rsp "$(echo -e "${CYAN}Nginx Basic Auth${NC} lozinka: ")" BASIC_PASS; echo
[[ -n "$BASIC_PASS" ]] && break
warn "Lozinka ne može biti prazna"
done
echo ""
echo -e "${CYAN}SSL certifikat?${NC}"
echo " L = Let's Encrypt (besplatno, treba valjana domena)"
echo " S = Self-signed (radi s IP adresom, browser pokazuje upozorenje)"
echo " N = Bez SSL (HTTP only)"
read -rp "Odabir [L/S/N]: " SSL_CHOICE
SSL_CHOICE=$(echo "${SSL_CHOICE:-n}" | tr '[:upper:]' '[:lower:]')
DO_SSL="n"; DO_SELFSIGNED="n"; LE_EMAIL=""
case "$SSL_CHOICE" in
l)
DO_SSL="y"
read -rp "$(echo -e "${CYAN}Email${NC} za Let's Encrypt: ")" LE_EMAIL
[[ -z "$LE_EMAIL" ]] && error "Email je obavezan za Let's Encrypt"
;;
s) DO_SELFSIGNED="y" ;;
esac
echo ""
read -rp "$(echo -e "${CYAN}GeoIP blokiranje${NC}? [y/N]: ")" DO_GEOIP
DO_GEOIP="${DO_GEOIP:-n}"
MM_ACCOUNT_ID=""; MM_LICENSE_KEY=""; GEO_COUNTRIES=""
if [[ "$DO_GEOIP" =~ ^[Yy]$ ]]; then
warn "Potreban besplatni MaxMind račun: https://www.maxmind.com/en/geolite2/signup"
read -rp "MaxMind Account ID: " MM_ACCOUNT_ID
read -rp "MaxMind License Key: " MM_LICENSE_KEY
read -rp "Dozvoljene zemlje (ISO kodovi, razmak, npr: HR BA SI): " GEO_COUNTRIES
fi
echo ""
read -rp "$(echo -e "${CYAN}Web GUI${NC} — instalirati web sučelje za upravljanje? [y/N]: ")" DO_WEBGUI
DO_WEBGUI="${DO_WEBGUI:-n}"
GUI_USER="admin"; GUI_PASS=""; GUI_PORT=5555; GUI_PATH="/guacadmin/"
if [[ "$DO_WEBGUI" =~ ^[Yy]$ ]]; then
read -rp "GUI korisničko ime [admin]: " GUI_USER
GUI_USER="${GUI_USER:-admin}"
while true; do
read -rsp "GUI lozinka (min 8 znakova): " GUI_PASS; echo
[[ ${#GUI_PASS} -ge 8 ]] && break
warn "Lozinka mora imati najmanje 8 znakova"
done
read -rp "GUI URL path [/guacadmin/]: " inp_path
GUI_PATH="${inp_path:-/guacadmin/}"
[[ "$GUI_PATH" != /* ]] && GUI_PATH="/${GUI_PATH}"
[[ "$GUI_PATH" != */ ]] && GUI_PATH="${GUI_PATH}/"
fi
# Generirane lozinke
POSTGRES_PASS=$(head -c 512 /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | cut -c1-32)
SECRET_KEY=$(openssl rand -hex 32)
GUI_SECRET=$(openssl rand -hex 32)
echo ""
echo -e "${BOLD}${YELLOW}════ Sačuvajte ove podatke ════${NC}"
echo -e " PostgreSQL lozinka : ${CYAN}${POSTGRES_PASS}${NC}"
echo -e " Guacamole admin : ${CYAN}guacadmin / guacadmin${NC} (promijenite odmah!)"
[[ "$DO_WEBGUI" =~ ^[Yy]$ ]] && echo -e " Web GUI : ${CYAN}${GUI_USER} / ${GUI_PASS}${NC}"
[[ "$DO_SELFSIGNED" =~ ^[Yy]$ ]] && echo -e " SSL tip : ${CYAN}Self-signed (browser upozorenje je normalno)${NC}"
echo -e "${BOLD}${YELLOW}════════════════════════════════${NC}"
echo ""
read -rp "Pritisnite Enter za nastavak..."
# ── Ažuriranje i preduvjeti ───────────────────────────────────────────────────
section "Ažuriranje i instalacija preduvjeta"
pkg_update
case "$PKG" in
apt)
pkg_install curl wget gnupg2 ca-certificates lsb-release \
openssl apache2-utils nginx fail2ban
;;
dnf|yum)
if [[ "$OS_ID" =~ ^(centos|rhel|rocky|almalinux|ol)$ ]]; then
pkg_install epel-release 2>/dev/null || true
pkg_update
fi
pkg_install curl wget gnupg2 ca-certificates openssl \
httpd-tools nginx fail2ban
;;
pacman) pkg_install curl wget openssl apache nginx fail2ban ;;
zypper) pkg_install curl wget openssl apache2-utils nginx fail2ban ;;
esac
info "Preduvjeti instalirani"
# ── Docker ────────────────────────────────────────────────────────────────────
section "Docker"
install_docker_apt() {
local distro_id="$1"
install -m 0755 -d /etc/apt/keyrings
curl -fsSL "https://download.docker.com/linux/${distro_id}/gpg" \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
local codename
codename=$(. /etc/os-release && echo "${VERSION_CODENAME:-$(lsb_release -cs 2>/dev/null)}")
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/${distro_id} ${codename} stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update -y
pkg_install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
}
if command -v docker &>/dev/null; then
info "Docker već instaliran: $(docker --version)"
else
info "Instaliranje Docker-a..."
case "$PKG" in
apt)
local_id="$OS_ID"
[[ "$OS_ID" =~ ^(linuxmint|pop)$ ]] && local_id="ubuntu"
[[ "$OS_ID" =~ ^(kali|raspbian)$ ]] && local_id="debian"
install_docker_apt "$local_id"
;;
dnf|yum)
pkg_install yum-utils
yum-config-manager --add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
pkg_install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
;;
pacman) pkg_install docker docker-compose ;;
zypper)
zypper addrepo https://download.docker.com/linux/opensuse/docker-ce.repo || true
zypper refresh
pkg_install docker-ce docker-ce-cli containerd.io docker-compose-plugin
;;
esac
systemctl enable --now docker
info "Docker instaliran: $(docker --version)"
fi
if docker compose version &>/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
info "Docker Compose plugin: $(docker compose version)"
elif command -v docker-compose &>/dev/null; then
COMPOSE_CMD="docker-compose"
info "Docker Compose standalone: $(docker-compose --version)"
else
error "Docker Compose nije pronađen. Reinstalirajte Docker."
fi
# ── Guacamole ─────────────────────────────────────────────────────────────────
section "Priprema Guacamole"
mkdir -p "${INSTALL_DIR}"/{extensions,db-init}
info "Radni direktorij: ${INSTALL_DIR}"
info "Preuzimanje SQL init skripte..."
docker run --rm "guacamole/guacamole:${GUAC_VERSION}" \
/opt/guacamole/bin/initdb.sh --postgresql \
> "${INSTALL_DIR}/db-init/initdb.sql"
info "SQL init skripta preuzeta"
section "TOTP 2FA ekstenzija"
# Image već sadrži TOTP jar u /opt/guacamole/totp — aktivira se s
# TOTP_ENABLED=true u compose datoteci (vidi niže). Ručno preuzimanje
# nije potrebno i stvorilo bi duplu ekstenziju.
info "TOTP se aktivira preko TOTP_ENABLED (ugrađen u image)"
cat > "${INSTALL_DIR}/.env" << EOF
POSTGRES_PASS=${POSTGRES_PASS}
GUAC_VERSION=${GUAC_VERSION}
SECRET_KEY=${SECRET_KEY}
EOF
chmod 600 "${INSTALL_DIR}/.env"
section "docker-compose.yml"
cat > "${INSTALL_DIR}/docker-compose.yml" << EOF
version: '3.8'
services:
guacd:
image: guacamole/guacd:${GUAC_VERSION}
container_name: guacd
restart: unless-stopped
networks: [guac_net]
postgres:
image: postgres:15-alpine
container_name: guac_postgres
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: \${POSTGRES_PASS}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db-init/initdb.sql:/docker-entrypoint-initdb.d/initdb.sql:ro
networks: [guac_net]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U guacamole_user -d guacamole_db"]
interval: 10s
timeout: 5s
retries: 10
guacamole:
image: guacamole/guacamole:${GUAC_VERSION}
container_name: guacamole
restart: unless-stopped
depends_on:
postgres: {condition: service_healthy}
guacd: {condition: service_started}
environment:
GUACD_HOSTNAME: guacd
POSTGRESQL_HOSTNAME: postgres
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USER: guacamole_user
POSTGRESQL_PASSWORD: \${POSTGRES_PASS}
TOTP_ENABLED: "true"
# Stvarni GUACAMOLE_HOME je /home/guacamole/.guacamole; ova varijabla
# označava predložak koji start.sh kopira u njega, pa je montirani
# extensions/ direktorij time stvarno u upotrebi.
GUACAMOLE_HOME: /etc/guacamole
volumes:
- ./extensions:/etc/guacamole/extensions:ro
ports:
- "127.0.0.1:${GUAC_PORT}:8080"
networks: [guac_net]
networks:
guac_net:
driver: bridge
volumes:
postgres_data:
EOF
info "docker-compose.yml kreiran"
section "Pokretanje kontejnera"
cd "${INSTALL_DIR}"
$COMPOSE_CMD up -d
info "Čekanje PostgreSQL (max 90s)..."
for i in $(seq 1 18); do
docker exec guac_postgres pg_isready -U guacamole_user -d guacamole_db &>/dev/null \
&& { info "PostgreSQL spreman"; break; }
[[ $i -eq 18 ]] && error "PostgreSQL timeout. Provjeri: docker logs guac_postgres"
sleep 5
done
info "Čekanje Guacamole aplikacije (30s)..."
sleep 30
docker ps --filter "name=guacamole" --filter "status=running" | grep -q guacamole \
|| error "Guacamole se nije pokrenuo. Provjeri: docker logs guacamole"
info "Guacamole kontejner aktivan"
# ── Nginx ─────────────────────────────────────────────────────────────────────
section "Nginx konfiguracija"
systemctl enable nginx
cat > /etc/nginx/conf.d/guacamole-maps.conf << 'EOF'
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Guacamole je Angular SPA i na jedno otvaranje ispali desetke
# zahtjeva; 10r/m je rusilo ucitavanje. Brute-force pokriva fail2ban.
limit_req_zone $binary_remote_addr zone=guac_req:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=guac_ws:10m rate=30r/m;
EOF
if [[ "$PKG" == "apt" ]]; then
NGINX_CONF="/etc/nginx/sites-available/guacamole"
NGINX_ENABLED="/etc/nginx/sites-enabled/guacamole"
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
rm -f /etc/nginx/sites-enabled/default
else
NGINX_CONF="/etc/nginx/conf.d/guacamole.conf"
NGINX_ENABLED=""
fi
cat > "$NGINX_CONF" << EOF
server {
listen 80;
server_name ${DOMAIN};
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location = / { return 301 /guacamole/; }
location /guacamole/ {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.guac_htpasswd;
limit_req zone=guac_req burst=50 nodelay;
proxy_pass http://127.0.0.1:${GUAC_PORT}/guacamole/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \$connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
}
}
EOF
[[ -n "$NGINX_ENABLED" ]] && ln -sf "$NGINX_CONF" "$NGINX_ENABLED"
nginx -t || error "Nginx konfiguracija sadrži grešku"
systemctl reload nginx
info "Nginx konfiguriran"
# ── Basic Auth ────────────────────────────────────────────────────────────────
section "HTTP Basic Auth"
htpasswd -bc /etc/nginx/.guac_htpasswd "$BASIC_USER" "$BASIC_PASS"
chmod 640 /etc/nginx/.guac_htpasswd
chown root:www-data /etc/nginx/.guac_htpasswd 2>/dev/null || \
chown root:nginx /etc/nginx/.guac_htpasswd 2>/dev/null || true
info "Basic Auth postavljen za: ${BASIC_USER}"
# ── fail2ban ──────────────────────────────────────────────────────────────────
section "fail2ban"
cat > /etc/fail2ban/filter.d/nginx-basicauth.conf << 'EOF'
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /guacamole.*" 401
ignoreregex =
EOF
cat > /etc/fail2ban/filter.d/nginx-guacamole.conf << 'EOF'
[Definition]
failregex = ^<HOST> .* "POST /guacamole/api/tokens HTTP.*" 403
ignoreregex =
EOF
cat > /etc/fail2ban/jail.d/guacamole.conf << EOF
[nginx-basicauth]
enabled = true
port = http,https
filter = nginx-basicauth
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 3600
[nginx-guacamole]
enabled = true
port = http,https
filter = nginx-guacamole
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 3600
[sshd]
enabled = true
maxretry = 3
findtime = 300
bantime = 86400
EOF
systemctl enable --now fail2ban
info "fail2ban konfiguriran"
# ── SSL ───────────────────────────────────────────────────────────────────────
if [[ "$DO_SSL" =~ ^[Yy]$ ]]; then
section "SSL (Let's Encrypt)"
case "$PKG" in
apt) pkg_install certbot python3-certbot-nginx ;;
dnf|yum) pkg_install certbot python3-certbot-nginx ;;
pacman) pkg_install certbot certbot-nginx ;;
zypper) pkg_install certbot python3-certbot-nginx || warn "certbot-nginx instaliraj ručno" ;;
esac
certbot --nginx -d "$DOMAIN" --email "$LE_EMAIL" \
--agree-tos --non-interactive --redirect
if systemctl list-unit-files certbot.timer &>/dev/null; then
systemctl enable --now certbot.timer
else
echo "0 3 * * * root certbot renew --quiet --post-hook 'systemctl reload nginx'" >> /etc/crontab
fi
nginx -t && systemctl reload nginx
info "SSL aktivan, auto-obnova postavljena"
fi
# ── Self-signed SSL ───────────────────────────────────────────────────────────
if [[ "$DO_SELFSIGNED" =~ ^[Yy]$ ]]; then
section "Self-signed SSL certifikat"
CERT_DIR="/etc/ssl/guacamole"
mkdir -p "$CERT_DIR"
# SAN: IP adresa ili domena
if [[ "${DOMAIN}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
SAN="IP:${DOMAIN}"
else
SAN="DNS:${DOMAIN}"
fi
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout "${CERT_DIR}/selfsigned.key" \
-out "${CERT_DIR}/selfsigned.crt" \
-subj "/CN=${DOMAIN}/O=Guacamole/C=HR" \
-addext "subjectAltName=${SAN}" 2>/dev/null
chmod 600 "${CERT_DIR}/selfsigned.key"
info "Self-signed certifikat kreiran (vrijedi 10 godina)"
# Prepiši nginx config za HTTPS
cat > "$NGINX_CONF" << EOF
# HTTP → HTTPS redirect
server {
listen 80;
server_name ${DOMAIN};
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl;
server_name ${DOMAIN};
ssl_certificate ${CERT_DIR}/selfsigned.crt;
ssl_certificate_key ${CERT_DIR}/selfsigned.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location = / { return 301 /guacamole/; }
location /guacamole/ {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.guac_htpasswd;
limit_req zone=guac_req burst=50 nodelay;
proxy_pass http://127.0.0.1:${GUAC_PORT}/guacamole/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \$connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
}
}
EOF
[[ -n "$NGINX_ENABLED" ]] && ln -sf "$NGINX_CONF" "$NGINX_ENABLED"
nginx -t && systemctl reload nginx
info "Nginx konfiguriran za HTTPS (self-signed)"
warn "Browser će pokazati upozorenje — to je normalno za self-signed certifikat"
warn "Chrome: klikni 'Napredno' → 'Nastavi na ${DOMAIN}'"
fi
# ── GeoIP ─────────────────────────────────────────────────────────────────────
if [[ "$DO_GEOIP" =~ ^[Yy]$ ]]; then
section "GeoIP blokiranje"
case "$PKG" in
apt) pkg_install libnginx-mod-http-geoip2 geoipupdate || warn "Provjeri apt repozitorij" ;;
dnf|yum) pkg_install nginx-mod-http-geoip2 || warn "Modul možda nije dostupan" ;;
pacman) warn "GeoIP na Arch-u: instaliraj ručno (AUR: nginx-mod-http-geoip2)" ;;
esac
if command -v geoipupdate &>/dev/null; then
mkdir -p /etc/GeoIP /usr/share/GeoIP
cat > /etc/GeoIP.conf << EOF
AccountID ${MM_ACCOUNT_ID}
LicenseKey ${MM_LICENSE_KEY}
EditionIDs GeoLite2-Country
DatabaseDirectory /usr/share/GeoIP
EOF
geoipupdate && info "GeoIP baza preuzeta" || warn "geoipupdate nije uspio"
fi
COUNTRY_MAP_ENTRIES=""
for cc in $GEO_COUNTRIES; do COUNTRY_MAP_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 "$COUNTRY_MAP_ENTRIES")}
EOF
sed -i '/auth_basic.*Restricted/i\ if ($allowed_country = 0) { return 403; }' "$NGINX_CONF"
nginx -t && systemctl reload nginx
info "GeoIP aktivan — dozvoljene: ${GEO_COUNTRIES}"
fi
# ── Firewall ──────────────────────────────────────────────────────────────────
section "Firewall"
if command -v ufw &>/dev/null; then
ufw allow 22/tcp comment "SSH" 2>/dev/null || true
ufw allow 80/tcp comment "HTTP" 2>/dev/null || true
ufw allow 443/tcp comment "HTTPS" 2>/dev/null || true
ufw --force enable
info "UFW pravila postavljena"
elif command -v firewall-cmd &>/dev/null; then
firewall-cmd --permanent --add-service={ssh,http,https} 2>/dev/null || true
firewall-cmd --reload
info "firewalld pravila postavljena"
else
warn "Firewall nije pronađen — konfigurirajte ručno"
fi
# ── Web GUI ───────────────────────────────────────────────────────────────────
if [[ "$DO_WEBGUI" =~ ^[Yy]$ ]]; then
section "Web GUI instalacija"
WEBGUI_DIR="${INSTALL_DIR}/webgui"
mkdir -p "${WEBGUI_DIR}/templates"
# Python
case "$PKG" in
apt) pkg_install python3 python3-pip python3-venv ;;
dnf|yum) pkg_install python3 python3-pip ;;
pacman) pkg_install python python-pip ;;
zypper) pkg_install python3 python3-pip ;;
esac
info "Python: $(python3 --version)"
# ── Aplikacija ──────────────────────────────────────────────
# Izvor je webgui/ pokraj ove skripte. Ranije je install.sh nosio
# vlastitu inline kopiju koja se razisla s repoom — nedostajale su
# stranice Backup i Azuriranja koje README dokumentira.
if [[ ! -f "${SRC_DIR}/webgui/app.py" ]]; then
error "Nedostaje ${SRC_DIR}/webgui/ — kopirajte cijeli repo na server, ne samo install.sh"
fi
cp "${SRC_DIR}/webgui/app.py" "${WEBGUI_DIR}/app.py"
cp "${SRC_DIR}/webgui/templates/"*.html "${WEBGUI_DIR}/templates/"
info "Web GUI kopiran ($(find "${SRC_DIR}/webgui/templates" -name '*.html' | wc -l) predlozaka)"
# ── config.json ─────────────────────────────────────────────
cat > "${WEBGUI_DIR}/config.json" << EOF
{"username": "${GUI_USER}", "password": "${GUI_PASS}"}
EOF
chmod 600 "${WEBGUI_DIR}/config.json"
# ── Flask virtualenv ─────────────────────────────────────────
python3 -m venv "${WEBGUI_DIR}/venv"
"${WEBGUI_DIR}/venv/bin/pip" install --quiet --upgrade pip
"${WEBGUI_DIR}/venv/bin/pip" install --quiet flask
info "Flask instaliran"
# ── Systemd servis ───────────────────────────────────────────
cat > /etc/systemd/system/guacamole-webgui.service << EOF
[Unit]
Description=Guacamole Web Management GUI
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
User=root
WorkingDirectory=${WEBGUI_DIR}
Environment=PORT=${GUI_PORT}
Environment=SECRET_KEY=${GUI_SECRET}
Environment=GUI_PATH=${GUI_PATH}
ExecStart=${WEBGUI_DIR}/venv/bin/python3 ${WEBGUI_DIR}/app.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now guacamole-webgui
info "Servis guacamole-webgui pokrenut"
# ── Nginx location za GUI ────────────────────────────────────
GUI_LOCATION="
# Guacamole Web GUI
location ${GUI_PATH} {
auth_basic \"Restricted Access\";
auth_basic_user_file /etc/nginx/.guac_htpasswd;
proxy_pass http://127.0.0.1:${GUI_PORT}/;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 3600s;
proxy_set_header X-Accel-Buffering no;
}"
python3 - << PYEOF
content = open('${NGINX_CONF}').read()
block = """${GUI_LOCATION}"""
idx = content.rfind('}')
open('${NGINX_CONF}', 'w').write(content[:idx] + block + '\n}')
PYEOF
nginx -t && systemctl reload nginx
info "Nginx location ${GUI_PATH} dodan"
fi
# ── Status provjera ───────────────────────────────────────────────────────────
section "Status servisa"
check_service() {
systemctl is-active --quiet "$1" 2>/dev/null \
&& info "$1: aktivan" || warn "$1: NIJE aktivan"
}
check_container() {
docker ps --filter "name=$1" --filter "status=running" | grep -q "$1" \
&& info "Docker [$1]: aktivan" || warn "Docker [$1]: NIJE aktivan"
}
check_service nginx
check_service fail2ban
check_service docker
check_container guacamole
check_container guacd
check_container guac_postgres
[[ "$DO_WEBGUI" =~ ^[Yy]$ ]] && check_service guacamole-webgui
# ── Finalni sažetak ───────────────────────────────────────────────────────────
section "Instalacija završena"
PROTO="http"
[[ "$DO_SSL" =~ ^[Yy]$ ]] && PROTO="https"
[[ "$DO_SELFSIGNED" =~ ^[Yy]$ ]] && PROTO="https"
echo ""
echo -e "${BOLD}${GREEN}══════════════════════════════════════════════════════${NC}"
echo -e "${BOLD} Guacamole:${NC}"
echo -e " ${CYAN}${PROTO}://${DOMAIN}/guacamole/${NC}"
echo ""
echo -e "${BOLD} Nginx Basic Auth:${NC} ${BASIC_USER} / [vaša lozinka]"
echo -e "${BOLD} Guacamole admin:${NC} guacadmin / guacadmin ${RED}← PROMIJENI!${NC}"
if [[ "$DO_WEBGUI" =~ ^[Yy]$ ]]; then
echo ""
echo -e "${BOLD} Web GUI:${NC}"
echo -e " ${CYAN}${PROTO}://${DOMAIN}${GUI_PATH}${NC}"
echo -e " Korisnik: ${GUI_USER} | Lozinka: ${GUI_PASS}"
fi
[[ "$DO_GEOIP" =~ ^[Yy]$ ]] && echo -e "\n${BOLD} GeoIP:${NC} dozvoljene države: ${GEO_COUNTRIES}"
echo -e "${BOLD}${GREEN}══════════════════════════════════════════════════════${NC}"
echo ""
# Spremi sve u credentials datoteku
CRED_FILE="${INSTALL_DIR}/credentials.txt"
cat > "$CRED_FILE" << EOF
Guacamole instalacija
=====================
Datum: $(date)
Guacamole URL : ${PROTO}://${DOMAIN}/guacamole/
Nginx Basic Auth: ${BASIC_USER} / ${BASIC_PASS}
Admin login : guacadmin / guacadmin (PROMIJENI!)
PostgreSQL pass : ${POSTGRES_PASS}
EOF
if [[ "$DO_SELFSIGNED" =~ ^[Yy]$ ]]; then
cat >> "$CRED_FILE" << EOF
SSL : Self-signed, vrijedi 10 god
Certifikat : /etc/ssl/guacamole/selfsigned.crt
Privatni ključ : /etc/ssl/guacamole/selfsigned.key
EOF
fi
if [[ "$DO_WEBGUI" =~ ^[Yy]$ ]]; then
cat >> "$CRED_FILE" << EOF
Web GUI URL : ${PROTO}://${DOMAIN}${GUI_PATH}
GUI korisnik : ${GUI_USER} / ${GUI_PASS}
EOF
fi
chmod 600 "$CRED_FILE"
info "Sve lozinke sačuvane u: ${CRED_FILE} (samo root može čitati)"