Guacamole Host Setup

From HyperSecurity Wiki
Revision as of 04:27, 1 October 2025 by Srapaz (talk | contribs) (Created page with "#!/usr/bin/env bash...")
(diff) ←Older revision | view current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
  1. !/usr/bin/env bash

set -euo pipefail

      1. --- Prompt Section ---

read -rp "Enter your domain (FQDN): " DOMAIN read -rp "Enter email for Let's Encrypt: " EMAIL read -rp "Enter the VNC username to create and configure: " VNC_USER

  1. Generate random passwords

ADMIN_PASS="$(openssl rand -base64 12)" DB_ROOT_PASS="$(openssl rand -base64 24 | tr -d '=+/')" DB_GUAC_PASS="$(openssl rand -base64 24 | tr -d '=+/')"

      1. --- Install dependencies (Debian/Ubuntu) ---

echo "[INFO] Installing required packages..." apt update apt install -y docker.io docker-compose-plugin nginx certbot python3-certbot-nginx ufw \

              curl whois net-tools xfce4 xfce4-goodies tightvncserver unzip                                                                                                                  
                                                                                                                                                                                             
  1. Decide compose command

if command -v docker-compose >/dev/null 2>&1; then

 COMPOSE="docker-compose"                                                                                                                                                                    

else

 COMPOSE="docker compose"                                                                                                                                                                    

fi

      1. --- Layout ---

APPDIR=/opt/guacamole NGXCONF=/etc/nginx/sites-enabled WEBROOT=/var/www/html ENVFILE="$APPDIR/.env" COMPOSEFILE="$APPDIR/docker-compose.yml" INITDIR="$APPDIR/initdb" GUAC_HOME="$APPDIR/guac-home"

mkdir -p "$APPDIR" "$INITDIR" "$GUAC_HOME/extensions" "$GUAC_HOME/lib"

      1. --- .env with secrets ---

install -m 0600 -o root -g root /dev/null "$ENVFILE" cat > "$ENVFILE" <<EOF GUAC_VERSION=1.5.5

  1. MariaDB

MYSQL_DATABASE=guacamole_db MYSQL_USER=guacamole_user MYSQL_PASSWORD=$DB_GUAC_PASS MYSQL_ROOT_PASSWORD=$DB_ROOT_PASS

  1. Paths

APPDIR=$APPDIR GUAC_HOME=$GUAC_HOME EOF chmod 600 "$ENVFILE"

      1. --- Docker network ---

docker network create guac-network >/dev/null 2>&1 || true

      1. --- docker-compose.yml ---

cat > "$COMPOSEFILE" <<'YAML' services:

 mariadb:                                                                                                                                                                                    
   image: mariadb:10.11                                                                                                                                                                      
   restart: unless-stopped                                                                                                                                                                   
   environment:                                                                                                                                                                              
     MYSQL_DATABASE: ${MYSQL_DATABASE}                                                                                                                                                       
     MYSQL_USER: ${MYSQL_USER}                                                                                                                                                               
     MYSQL_PASSWORD: ${MYSQL_PASSWORD}                                                                                                                                                       
     MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}                                                                                                                                             
   volumes:                                                                                                                                                                                  
     - ${APPDIR}/mariadb:/var/lib/mysql                                                                                                                                                      
     - ${APPDIR}/initdb:/docker-entrypoint-initdb.d:ro                                                                                                                                       
   healthcheck:                                                                                                                                                                              
     test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u${MYSQL_USER} -p${MYSQL_PASSWORD} || exit 1"]                                                                                       
     interval: 10s                                                                                                                                                                           
     timeout: 5s                                                                                                                                                                             
     retries: 20                                                                                                                                                                             
   networks: [guac-network]                                                                                                                                                                  
                                                                                                                                                                                             
 guacd:                                                                                                                                                                                      
   image: guacamole/guacd:${GUAC_VERSION}                                                                                                                                                    
   restart: unless-stopped                                                                                                                                                                   
   healthcheck:                                                                                                                                                                              
     test: ["CMD-SHELL", "nc -z 127.0.0.1 4822 || exit 1"]                                                                                                                                   
     interval: 10s                                                                                                                                                                           
     timeout: 5s                                                                                                                                                                             
     retries: 20                                                                                                                                                                             
   networks: [guac-network]                                                                                                                                                                  
                                                                                                                                                                                             
 guacamole:                                                                                                                                                                                  
   image: guacamole/guacamole:${GUAC_VERSION}                                                                                                                                                
   restart: unless-stopped                                                                                                                                                                   
   depends_on:                                                                                                                                                                               
     mariadb:                                                                                                                                                                                
       condition: service_healthy                                                                                                                                                            
     guacd:                                                                                                                                                                                  
       condition: service_healthy                                                                                                                                                            
   environment:                                                                                                                                                                              
     GUACD_HOSTNAME: guacd                                                                                                                                                                   
     MYSQL_HOSTNAME: mariadb                                                                                                                                                                 
     MYSQL_DATABASE: ${MYSQL_DATABASE}                                                                                                                                                       
     MYSQL_USER: ${MYSQL_USER}                                                                                                                                                               
     MYSQL_PASSWORD: ${MYSQL_PASSWORD}                                                                                                                                                       
     GUACAMOLE_HOME: /guac-home                                                                                                                                                              
   volumes:                                                                                                                                                                                  
     - ${GUAC_HOME}:/guac-home                                                                                                                                                               
   healthcheck:                                                                                                                                                                              
     test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/guacamole/ || exit 1"]                                                                                                             
     interval: 15s                                                                                                                                                                           
     timeout: 5s                                                                                                                                                                             
     retries: 20                                                                                                                                                                             
   networks: [guac-network]                                                                                                                                                                  
                                                                                                                                                                                             

networks:

 guac-network:                                                                                                                                                                               
   external: true                                                                                                                                                                            

YAML

      1. --- Initialize DB schema & JDBC extension ---

echo "[INFO] Fetching JDBC auth package..." cd "$APPDIR" curl -fsSLo guacamole-auth-jdbc.tgz \

 https://downloads.apache.org/guacamole/${GUAC_VERSION}/binary/guacamole-auth-jdbc-${GUAC_VERSION}.tar.gz                                                                                    

tar -xzf guacamole-auth-jdbc.tgz cp -f guacamole-auth-jdbc-${GUAC_VERSION}/mysql/*.sql "$INITDIR/" cp -f guacamole-auth-jdbc-${GUAC_VERSION}/mysql/guacamole-auth-jdbc-mysql-${GUAC_VERSION}.jar "$GUAC_HOME/extensions/"

      1. --- Bring up DB & guacd so initdb runs ---

$COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" up -d mariadb guacd echo "[INFO] Waiting for MariaDB to be healthy..." $COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" ps

  1. Start guacamole after DB is healthy

$COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" up -d guacamole

      1. --- Set guacadmin password in DB (salt + SHA-256) ---

echo "[INFO] Setting guacadmin password in DB..." SALT="$(openssl rand -hex 16)" HASH="$(printf '%s%s' "$ADMIN_PASS" "$SALT" | openssl dgst -sha256 -binary | od -An -t x1 | tr -d ' \n')"

docker exec -i "$($COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" ps -q mariadb)" \

 mysql -uroot -p"$DB_ROOT_PASS" guacamole_db <<SQL                                                                                                                                           

UPDATE guacamole_user

  SET password_salt = UNHEX('$SALT'),                                                                                                                                                        
      password_hash = UNHEX('$HASH'),                                                                                                                                                        
      disabled = 0                                                                                                                                                                           
WHERE username='guacadmin';                                                                                                                                                                  

SQL

      1. --- Nginx ACME (HTTP) site for webroot ---

echo "[INFO] Preparing Nginx HTTP challenge site..." mkdir -p "$WEBROOT/.well-known/acme-challenge"

cat > "$NGXCONF/guac-acme.conf" <<EOF server {

   listen 80;                                                                                                                                                                                
   server_name $DOMAIN;                                                                                                                                                                      
                                                                                                                                                                                             
   location /.well-known/acme-challenge/ {                                                                                                                                                   
       root $WEBROOT;                                                                                                                                                                        
       try_files \$uri =404;                                                                                                                                                                 
   }                                                                                                                                                                                         
                                                                                                                                                                                             
   location / {                                                                                                                                                                              
       return 301 https://\$host\$request_uri;                                                                                                                                               
   }                                                                                                                                                                                         

} EOF

nginx -t && systemctl reload nginx

      1. --- Obtain cert ---

echo "[INFO] Requesting Let's Encrypt certificate..." certbot certonly --webroot -w "$WEBROOT" -d "$DOMAIN" \

 --agree-tos -m "$EMAIL" --no-eff-email --non-interactive                                                                                                                                    
                                                                                                                                                                                             
      1. --- Nginx TLS reverse proxy (WebSockets, HSTS) ---

echo "[INFO] Writing Nginx TLS proxy..." cat > "$NGXCONF/guacamole.conf" <<'EOF' map $http_upgrade $connection_upgrade { default upgrade; close; }

server {

   listen 443 ssl http2;                                                                                                                                                                     
   server_name __DOMAIN__;                                                                                                                                                                   
                                                                                                                                                                                             
   ssl_certificate /etc/letsencrypt/live/__DOMAIN__/fullchain.pem;                                                                                                                           
   ssl_certificate_key /etc/letsencrypt/live/__DOMAIN__/privkey.pem;                                                                                                                         
                                                                                                                                                                                             
   # Security                                                                                                                                                                                
   ssl_protocols TLSv1.2 TLSv1.3;                                                                                                                                                            
   ssl_ciphers HIGH:!aNULL:!MD5;                                                                                                                                                             
   add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;                                                                                                        
   add_header X-Frame-Options DENY always;                                                                                                                                                   
   add_header X-Content-Type-Options nosniff always;                                                                                                                                         
                                                                                                                                                                                             
   # Keep ACME available on 443 as well (optional)                                                                                                                                           
   location /.well-known/acme-challenge/ {                                                                                                                                                   
       root /var/www/html;                                                                                                                                                                   
   }                                                                                                                                                                                         
                                                                                                                                                                                             
   # Guacamole (Tomcat inside container exposes /guacamole/)                                                                                                                                 
   location / {                                                                                                                                                                              
       proxy_pass http://127.0.0.1:8080/guacamole/;                                                                                                                                          
       proxy_http_version 1.1;                                                                                                                                                               
       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_buffering off;                                                                                                                                                                  
       proxy_read_timeout 3600s;                                                                                                                                                             
       proxy_send_timeout 3600s;                                                                                                                                                             
                                                                                                                                                                                             
       # WebSockets                                                                                                                                                                          
       proxy_set_header Upgrade $http_upgrade;                                                                                                                                               
       proxy_set_header Connection $connection_upgrade;                                                                                                                                      
   }                                                                                                                                                                                         

}

  1. HTTP kept for redirect + ACME (certbot renews against 80)

server {

   listen 80;                                                                                                                                                                                
   server_name __DOMAIN__;                                                                                                                                                                   
   location /.well-known/acme-challenge/ { root /var/www/html; }                                                                                                                             
   location / { return 301 https://$host$request_uri; }                                                                                                                                      

} EOF

  1. Inject the actual domain into the file

sed -i "s/__DOMAIN__/$DOMAIN/g" "$NGXCONF/guacamole.conf"

nginx -t && systemctl reload nginx

      1. --- Firewall ---

ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw --force enable

      1. --- Certbot renew (daily) ---

( crontab -l 2>/dev/null; echo "12 3 * * * certbot renew --quiet && systemctl reload nginx" ) | crontab -

      1. --- VNC user (optional workstation target) ---

echo "[INFO] Configuring VNC user: $VNC_USER" adduser --disabled-password --gecos "" "$VNC_USER" || true mkdir -p "/home/$VNC_USER/.vnc" echo "Set VNC password for user $VNC_USER:" su - "$VNC_USER" -c "vncpasswd"

cat > "/home/$VNC_USER/.vnc/xstartup" <<'EOS'

  1. !/bin/bash

xrdb $HOME/.Xresources startxfce4 & EOS chmod +x "/home/$VNC_USER/.vnc/xstartup" chown -R "$VNC_USER:$VNC_USER" "/home/$VNC_USER/.vnc"

cat > "/etc/systemd/system/vncserver@.service" <<'EOS' [Unit] Description=Start TightVNC server at startup After=network.target

[Service] Type=forking User=%i PAMName=login PIDFile=/home/%i/.vnc/%H:1.pid ExecStartPre=-/usr/bin/vncserver -kill :1 > /dev/null 2>&1 ExecStart=/usr/bin/vncserver :1 ExecStop=/usr/bin/vncserver -kill :1

[Install] WantedBy=multi-user.target EOS

systemctl daemon-reload systemctl enable vncserver@"$VNC_USER" systemctl start vncserver@"$VNC_USER"

      1. --- Done ---

echo echo "[INFO] Guacamole is ready at: https://$DOMAIN/" echo "[INFO] Login: guacadmin" echo "[INFO] Password: $ADMIN_PASS" echo "[INFO] GUACAMOLE_HOME: $GUAC_HOME" echo "[INFO] .env stored at: $ENVFILE (chmod 600)"