Last active
December 16, 2025 03:15
-
-
Save tonidy/f67f6af974a3f34bcf94d0e849b64a40 to your computer and use it in GitHub Desktop.
Custom Disco Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| set -eu | |
| # Log file for debugging | |
| LOG_FILE="/tmp/disco-install.log" | |
| # Helper function for logging | |
| log_step() { | |
| echo "-> $1" | |
| } | |
| # Helper function for error exits | |
| error_exit() { | |
| local error_message="$1" | |
| echo "Error: $error_message" | |
| echo "" | |
| echo "Something went wrong during the installation." | |
| echo "You can review the full log for details: less /tmp/disco-install.log" | |
| echo "" | |
| echo "For help, please join our Discord and share the relevant parts of the log:" | |
| echo " https://discord.gg/7J4vb5uUwU" | |
| echo "" | |
| exit 1 | |
| } | |
| TOTAL_STEPS=7 | |
| # Pre-flight checks | |
| log_step "[1/$TOTAL_STEPS] Checking system compatibility..." | |
| # Check if root or sudo available | |
| if [ "$(id -u)" -eq 0 ]; then | |
| SUDO_CMD="" | |
| else | |
| SUDO_CMD="sudo" | |
| # Test passwordless sudo | |
| if ! $SUDO_CMD -n true 2>/dev/null; then | |
| error_exit "This script requires passwordless sudo access. Please configure passwordless sudo or run as root." | |
| fi | |
| fi | |
| # Check OS version | |
| if [ -f /etc/os-release ]; then | |
| . /etc/os-release | |
| if [ "$ID" != "ubuntu" ] || [ "$VERSION_ID" != "24.04" ]; then | |
| error_exit "This installer only supports Ubuntu 24.04 LTS. Detected: $ID $VERSION_ID" | |
| fi | |
| else | |
| error_exit "Cannot detect OS version. /etc/os-release not found." | |
| fi | |
| # Check for curl and install if needed | |
| if ! command -v curl >/dev/null 2>&1; then | |
| log_step "Installing curl..." | |
| $SUDO_CMD apt-get update -qq >> "$LOG_FILE" 2>&1 | |
| $SUDO_CMD apt-get install -y curl >> "$LOG_FILE" 2>&1 | |
| fi | |
| # Check for jq | |
| if ! command -v jq >/dev/null 2>&1; then | |
| $SUDO_CMD apt-get update -qq >> "$LOG_FILE" 2>&1 | |
| $SUDO_CMD apt-get install -y jq >> "$LOG_FILE" 2>&1 | |
| fi | |
| # Check if Docker is already installed (likely means installer was already run) | |
| #if command -v docker >/dev/null 2>&1; then | |
| # error_exit "Docker is already installed on this system. This installer is designed for fresh Ubuntu 24.04 servers. If you've already run this installer, your Disco instance should already be set up." | |
| #fi | |
| # Provision domain | |
| log_step "[2/$TOTAL_STEPS] Provisioning secure domain..." | |
| DOMAIN_RESPONSE=$(curl -sSL -X POST https://backend.disco.cloud/api/installer/provision-domain) | |
| DISCO_HOST=$(echo "$DOMAIN_RESPONSE" | jq -r '.domain') | |
| PUBLIC_IP=$(echo "$DOMAIN_RESPONSE" | jq -r '.public_ip') | |
| if [ -z "$DISCO_HOST" ] || [ "$DISCO_HOST" = "null" ]; then | |
| echo "Response: $DOMAIN_RESPONSE" >> "$LOG_FILE" | |
| error_exit "Failed to provision domain." | |
| fi | |
| if [ -z "$PUBLIC_IP" ] || [ "$PUBLIC_IP" = "null" ]; then | |
| echo "Response: $DOMAIN_RESPONSE" >> "$LOG_FILE" | |
| error_exit "Failed to get public IP from provisioning service." | |
| fi | |
| # Install Docker (using convenience script with retry for apt locks) | |
| if ! command -v docker >/dev/null 2>&1; then | |
| log_step "[3/$TOTAL_STEPS] Installing Docker..." | |
| export DEBIAN_FRONTEND=noninteractive | |
| curl -fsSL https://get.docker.com -o /tmp/get-docker.sh | |
| # Retry if apt locks prevent installation | |
| MAX_DOCKER_ATTEMPTS=10 | |
| DOCKER_ATTEMPT=0 | |
| while [ $DOCKER_ATTEMPT -lt $MAX_DOCKER_ATTEMPTS ]; do | |
| if $SUDO_CMD sh /tmp/get-docker.sh >> "$LOG_FILE" 2>&1; then | |
| break | |
| fi | |
| # Check if it was an apt lock issue | |
| if tail -5 "$LOG_FILE" | grep -q "lock"; then | |
| DOCKER_ATTEMPT=$((DOCKER_ATTEMPT + 1)) | |
| if [ $DOCKER_ATTEMPT -lt $MAX_DOCKER_ATTEMPTS ]; then | |
| sleep 5 | |
| continue | |
| fi | |
| fi | |
| # Non-lock error or max attempts reached | |
| error_exit "Failed to install Docker. The Docker installation script failed." | |
| done | |
| rm /tmp/get-docker.sh | |
| fi | |
| # Initialize Disco daemon | |
| log_step "[4/$TOTAL_STEPS] Starting Disco daemon..." | |
| DISCO_IMAGE="letsdiscodev/daemon:latest" | |
| INIT_OUTPUT=$($SUDO_CMD docker run \ | |
| --rm \ | |
| --mount source=disco-data,target=/disco/data \ | |
| --mount type=bind,source=/var/run,target=/host/var/run \ | |
| --mount type=bind,source=/etc,target=/host/etc \ | |
| --mount type=bind,source=$HOME,target=/host/$HOME \ | |
| --mount source=disco-caddy-init-config,target=/initconfig \ | |
| --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \ | |
| --env DISCO_HOST="$DISCO_HOST" \ | |
| --env DISCO_ADVERTISE_ADDR="$PUBLIC_IP" \ | |
| --env HOST_HOME=$HOME \ | |
| --env DISCO_IMAGE=$DISCO_IMAGE \ | |
| $DISCO_IMAGE \ | |
| disco_init 2>&1) | |
| # Extract API key from output | |
| API_KEY=$(echo "$INIT_OUTPUT" | grep -o 'Created API key: [a-z0-9]\{32\}' | awk '{print $4}') | |
| if [ -z "$API_KEY" ]; then | |
| echo "Output:" >> "$LOG_FILE" | |
| echo "$INIT_OUTPUT" >> "$LOG_FILE" | |
| error_exit "Failed to extract API key from initialization output." | |
| fi | |
| # Wait for daemon to become available (SSL cert + API ready) | |
| log_step "[5/$TOTAL_STEPS] Waiting for Disco daemon to become available..." | |
| MAX_ATTEMPTS=20 # 20 attempts * 15 seconds (10s timeout + 5s sleep) = 5 minutes max | |
| ATTEMPT=0 | |
| while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do | |
| # Use -k to ignore initial self-signed/invalid cert errors during Let's Encrypt setup | |
| # Check if daemon is responding with {"disco": true} | |
| # Use --max-time to prevent hanging if port is blocked | |
| RESPONSE=$(curl -k -sS --max-time 10 "https://$DISCO_HOST/" 2>/dev/null || true) | |
| if echo "$RESPONSE" | jq -e '.disco == true' >/dev/null 2>&1; then | |
| break | |
| fi | |
| sleep 5 | |
| ATTEMPT=$((ATTEMPT + 1)) | |
| done | |
| if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then | |
| echo "Timed out waiting for the Disco daemon to become available at https://$DISCO_HOST." >> "$LOG_FILE" | |
| echo "" >> "$LOG_FILE" | |
| echo "This can happen for a few reasons:" >> "$LOG_FILE" | |
| echo " 1. The server is behind a firewall or NAT that is blocking inbound traffic on port 443." >> "$LOG_FILE" | |
| echo " 2. DNS has not yet propagated for $DISCO_HOST." >> "$LOG_FILE" | |
| echo " 3. The Disco daemon container failed to start." >> "$LOG_FILE" | |
| echo "" >> "$LOG_FILE" | |
| echo "Troubleshooting steps:" >> "$LOG_FILE" | |
| echo " - Ensure your server's firewall allows inbound TCP traffic on port 443." >> "$LOG_FILE" | |
| echo " - Check container logs with: sudo docker service logs disco" >> "$LOG_FILE" | |
| error_exit "Timed out waiting for the Disco daemon to become available at https://$DISCO_HOST. This usually means port 443 is blocked by a firewall." | |
| fi | |
| # Generate invite URL from the Disco daemon | |
| log_step "[6/$TOTAL_STEPS] Generating invite link..." | |
| # Encode API key with trailing colon for Basic auth | |
| AUTH_TOKEN=$(echo -n "$API_KEY:" | base64) | |
| INVITE_RESPONSE=$(curl -sSL -X POST "https://$DISCO_HOST/api/api-key-invites" \ | |
| -H "Accept: application/json" \ | |
| -H "Authorization: Basic $AUTH_TOKEN" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"name": "installer.sh-ui"}') | |
| INVITE_URL=$(echo "$INVITE_RESPONSE" | jq -r '.apiKeyInvite.url') | |
| if [ -z "$INVITE_URL" ] || [ "$INVITE_URL" = "null" ]; then | |
| echo "Response: $INVITE_RESPONSE" >> "$LOG_FILE" | |
| error_exit "Failed to generate invite URL." | |
| fi | |
| # Generate final dashboard URL | |
| FINAL_URL="https://dashboard.disco.cloud/accept-invite?inviteUrl=$INVITE_URL" | |
| # Shorten the URL via the backend | |
| SHORT_URL_RESPONSE=$(curl -sSL -X POST "https://backend.disco.cloud/api/short-urls" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{\"url\":\"$FINAL_URL\"}" 2>/dev/null || echo "") | |
| # Extract the short URL from the response, fallback to FINAL_URL if it fails | |
| if [ -n "$SHORT_URL_RESPONSE" ]; then | |
| SHORT_URL=$(echo "$SHORT_URL_RESPONSE" | jq -r '.url' 2>/dev/null || echo "$FINAL_URL") | |
| if [ -z "$SHORT_URL" ] || [ "$SHORT_URL" = "null" ]; then | |
| SHORT_URL="$FINAL_URL" | |
| fi | |
| else | |
| SHORT_URL="$FINAL_URL" | |
| fi | |
| log_step "[7/$TOTAL_STEPS] Installation complete!" | |
| echo "" | |
| echo "Visit this URL to complete setup:" | |
| echo "" | |
| echo " $SHORT_URL" | |
| echo "" | |
| echo "You can now exit SSH - your Disco instance is running." | |
| echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment