Difference between revisions of "Guacamole Host Setup"
(Created page with "#!/usr/bin/env bash...") |
|||
Line 1: | Line 1: | ||
+ | |||
+ | <pre> | ||
#!/usr/bin/env bash | #!/usr/bin/env bash | ||
set -euo pipefail | set -euo pipefail | ||
Line 283: | Line 285: | ||
echo "[INFO] GUACAMOLE_HOME: $GUAC_HOME" | echo "[INFO] GUACAMOLE_HOME: $GUAC_HOME" | ||
echo "[INFO] .env stored at: $ENVFILE (chmod 600)" | echo "[INFO] .env stored at: $ENVFILE (chmod 600)" | ||
+ | </pre> |
Revision as of 04:33, 1 October 2025
#!/usr/bin/env bash set -euo pipefail ### --- 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 # 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 '=+/')" ### --- 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 # Decide compose command if command -v docker-compose >/dev/null 2>&1; then COMPOSE="docker-compose" else COMPOSE="docker compose" fi ### --- 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" ### --- .env with secrets --- install -m 0600 -o root -g root /dev/null "$ENVFILE" cat > "$ENVFILE" <<EOF GUAC_VERSION=1.5.5 # MariaDB MYSQL_DATABASE=guacamole_db MYSQL_USER=guacamole_user MYSQL_PASSWORD=$DB_GUAC_PASS MYSQL_ROOT_PASSWORD=$DB_ROOT_PASS # Paths APPDIR=$APPDIR GUAC_HOME=$GUAC_HOME EOF chmod 600 "$ENVFILE" ### --- Docker network --- docker network create guac-network >/dev/null 2>&1 || true ### --- 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 ### --- 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/" ### --- 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 # Start guacamole after DB is healthy $COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" up -d guacamole ### --- 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 ### --- 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 ### --- 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 ### --- 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; } } # 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 # Inject the actual domain into the file sed -i "s/__DOMAIN__/$DOMAIN/g" "$NGXCONF/guacamole.conf" nginx -t && systemctl reload nginx ### --- Firewall --- ufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw --force enable ### --- Certbot renew (daily) --- ( crontab -l 2>/dev/null; echo "12 3 * * * certbot renew --quiet && systemctl reload nginx" ) | crontab - ### --- 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' #!/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" ### --- 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)"