Skip to content

Instantly share code, notes, and snippets.

@tonidy
Last active December 16, 2025 03:15
Show Gist options
  • Select an option

  • Save tonidy/f67f6af974a3f34bcf94d0e849b64a40 to your computer and use it in GitHub Desktop.

Select an option

Save tonidy/f67f6af974a3f34bcf94d0e849b64a40 to your computer and use it in GitHub Desktop.
Custom Disco Script
#!/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