Created
February 17, 2026 08:55
-
-
Save drhema/e80577850b61912c348ababb294c2f78 to your computer and use it in GitHub Desktop.
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/bash | |
| set -euo pipefail | |
| # ============================================ | |
| # Dublyo PaaS — Server Provisioning Script | |
| # Sets up Docker, Traefik v3.6, Portainer CE | |
| # on a fresh Ubuntu 24.04 server | |
| # ============================================ | |
| echo "========================================" | |
| echo " Dublyo Server Setup" | |
| echo " Docker + Traefik + Portainer" | |
| echo "========================================" | |
| # ------------------------------------------- | |
| # CONFIGURATION — Edit these values | |
| # ------------------------------------------- | |
| # Cloudflare credentials (for DNS challenge SSL) | |
| CF_API_EMAIL="${CF_API_EMAIL:-your-email@example.com}" | |
| CF_DNS_API_TOKEN="${CF_DNS_API_TOKEN:-your-cloudflare-dns-api-token}" | |
| # Portainer admin password (change this!) | |
| PORTAINER_PASSWORD="${PORTAINER_PASSWORD:-$(openssl rand -base64 24)}" | |
| # Base directory | |
| DUBLYO_DIR="/opt/dublyo" | |
| echo "[CONFIG] Base directory: $DUBLYO_DIR" | |
| echo "[CONFIG] Portainer password: $PORTAINER_PASSWORD" | |
| echo "" | |
| echo ">>> SAVE THIS PASSWORD! <<<" | |
| echo "" | |
| # ------------------------------------------- | |
| # STEP 1: Disable auto-updates & clear locks | |
| # ------------------------------------------- | |
| echo "[1/10] Disabling auto-updates..." | |
| systemctl stop unattended-upgrades 2>/dev/null || true | |
| systemctl disable unattended-upgrades 2>/dev/null || true | |
| systemctl stop apt-daily.timer 2>/dev/null || true | |
| systemctl stop apt-daily-upgrade.timer 2>/dev/null || true | |
| systemctl disable apt-daily.timer 2>/dev/null || true | |
| systemctl disable apt-daily-upgrade.timer 2>/dev/null || true | |
| pkill -9 apt 2>/dev/null || true | |
| pkill -9 dpkg 2>/dev/null || true | |
| pkill -9 apt-get 2>/dev/null || true | |
| rm -f /var/lib/dpkg/lock-frontend 2>/dev/null || true | |
| rm -f /var/lib/apt/lists/lock 2>/dev/null || true | |
| rm -f /var/lib/dpkg/lock 2>/dev/null || true | |
| rm -f /var/cache/apt/archives/lock 2>/dev/null || true | |
| dpkg --configure -a 2>/dev/null || true | |
| # Helper: wait for apt lock | |
| wait_for_apt() { | |
| while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || fuser /var/lib/dpkg/lock >/dev/null 2>&1; do | |
| echo " Waiting for apt lock..." | |
| sleep 3 | |
| done | |
| } | |
| echo "[1/10] Done" | |
| # ------------------------------------------- | |
| # STEP 2: Update system packages | |
| # ------------------------------------------- | |
| echo "[2/10] Updating system..." | |
| wait_for_apt | |
| apt-get update -q | |
| wait_for_apt | |
| DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -q | |
| echo "[2/10] Done" | |
| # ------------------------------------------- | |
| # STEP 3: Install dependencies | |
| # ------------------------------------------- | |
| echo "[3/10] Installing dependencies..." | |
| wait_for_apt | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y -q \ | |
| ca-certificates curl gnupg jq fail2ban ufw | |
| echo "[3/10] Done" | |
| # ------------------------------------------- | |
| # STEP 4: Configure firewall (UFW) | |
| # ------------------------------------------- | |
| echo "[4/10] Configuring firewall..." | |
| ufw default deny incoming | |
| ufw default allow outgoing | |
| ufw allow 22/tcp # SSH | |
| ufw allow 80/tcp # HTTP (Traefik) | |
| ufw allow 443/tcp # HTTPS (Traefik) | |
| ufw allow 9443/tcp # Portainer UI | |
| echo "y" | ufw enable | |
| echo "[4/10] Done — Ports open: 22, 80, 443, 9443" | |
| # ------------------------------------------- | |
| # STEP 5: Install Docker | |
| # ------------------------------------------- | |
| echo "[5/10] Installing Docker..." | |
| install -m 0755 -d /etc/apt/keyrings | |
| curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc | |
| chmod a+r /etc/apt/keyrings/docker.asc | |
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null | |
| wait_for_apt | |
| apt-get update -q | |
| wait_for_apt | |
| DEBIAN_FRONTEND=noninteractive apt-get install -y -q \ | |
| docker-ce docker-ce-cli containerd.io docker-compose-plugin | |
| systemctl enable docker | |
| systemctl start docker | |
| echo "[5/10] Done — $(docker --version)" | |
| # ------------------------------------------- | |
| # STEP 6: Clean up old Docker data (if any) | |
| # ------------------------------------------- | |
| echo "[6/10] Cleaning up old Docker data..." | |
| docker stop traefik portainer 2>/dev/null || true | |
| docker rm traefik portainer 2>/dev/null || true | |
| docker volume rm portainer_data 2>/dev/null || true | |
| docker volume rm traefik_letsencrypt 2>/dev/null || true | |
| docker volume rm dublyo_portainer_data 2>/dev/null || true | |
| docker volume rm dublyo_traefik_letsencrypt 2>/dev/null || true | |
| docker network rm dublyo-public 2>/dev/null || true | |
| echo "[6/10] Done" | |
| # ------------------------------------------- | |
| # STEP 7: Create directory structure & configs | |
| # ------------------------------------------- | |
| echo "[7/10] Creating directory structure..." | |
| mkdir -p $DUBLYO_DIR/{traefik,certs,acme} | |
| # --- Traefik docker-compose --- | |
| cat > $DUBLYO_DIR/docker-compose.traefik.yml << 'TRAEFIKEOF' | |
| services: | |
| traefik: | |
| image: traefik:v3.6 | |
| container_name: traefik | |
| restart: unless-stopped | |
| command: | |
| - "--api.dashboard=false" | |
| - "--providers.docker=true" | |
| - "--providers.docker.exposedbydefault=false" | |
| - "--providers.docker.network=dublyo-public" | |
| - "--providers.file.directory=/etc/traefik/dynamic" | |
| - "--entrypoints.web.address=:80" | |
| - "--entrypoints.websecure.address=:443" | |
| - "--certificatesresolvers.httpchallenge.acme.email=ssl@dublyo.com" | |
| - "--certificatesresolvers.httpchallenge.acme.storage=/acme/acme.json" | |
| - "--certificatesresolvers.httpchallenge.acme.httpchallenge.entrypoint=web" | |
| - "--log.level=INFO" | |
| environment: | |
| - DOCKER_API_VERSION=1.44 | |
| ports: | |
| - "80:80" | |
| - "443:443" | |
| volumes: | |
| - /var/run/docker.sock:/var/run/docker.sock:ro | |
| - /opt/dublyo/traefik:/etc/traefik/dynamic:ro | |
| - /opt/dublyo/certs:/certs:ro | |
| - /opt/dublyo/acme:/acme | |
| networks: | |
| - dublyo-public | |
| networks: | |
| dublyo-public: | |
| name: dublyo-public | |
| driver: bridge | |
| TRAEFIKEOF | |
| # --- Portainer docker-compose --- | |
| cat > $DUBLYO_DIR/docker-compose.portainer.yml << 'PORTAINEREOF' | |
| services: | |
| portainer: | |
| image: portainer/portainer-ce:latest | |
| container_name: portainer | |
| restart: unless-stopped | |
| ports: | |
| - "9443:9443" | |
| volumes: | |
| - /var/run/docker.sock:/var/run/docker.sock | |
| - portainer_data:/data | |
| networks: | |
| - dublyo-public | |
| volumes: | |
| portainer_data: | |
| networks: | |
| dublyo-public: | |
| external: true | |
| PORTAINEREOF | |
| # --- Cloudflare env file --- | |
| cat > $DUBLYO_DIR/.env << ENVEOF | |
| CF_API_EMAIL=$CF_API_EMAIL | |
| CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN | |
| ENVEOF | |
| echo "[7/10] Done" | |
| # ------------------------------------------- | |
| # STEP 8: Generate SSL certificate | |
| # ------------------------------------------- | |
| echo "[8/10] Generating self-signed origin certificate..." | |
| # Self-signed cert for Cloudflare Full SSL mode (origin) | |
| openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ | |
| -keyout $DUBLYO_DIR/certs/origin.key \ | |
| -out $DUBLYO_DIR/certs/origin.crt \ | |
| -subj "/CN=*.dublyo.co" \ | |
| -addext "subjectAltName=DNS:*.dublyo.co,DNS:dublyo.co" \ | |
| 2>/dev/null | |
| # ACME storage file with correct permissions | |
| touch $DUBLYO_DIR/acme/acme.json | |
| chmod 600 $DUBLYO_DIR/acme/acme.json | |
| # Traefik dynamic TLS config (uses self-signed cert as default) | |
| cat > $DUBLYO_DIR/traefik/tls.yml << 'TLSEOF' | |
| tls: | |
| certificates: | |
| - certFile: /certs/origin.crt | |
| keyFile: /certs/origin.key | |
| stores: | |
| default: | |
| defaultCertificate: | |
| certFile: /certs/origin.crt | |
| keyFile: /certs/origin.key | |
| TLSEOF | |
| echo "[8/10] Done" | |
| # ------------------------------------------- | |
| # STEP 9: Start Traefik & Portainer | |
| # ------------------------------------------- | |
| echo "[9/10] Starting Traefik..." | |
| cd $DUBLYO_DIR | |
| docker compose -f docker-compose.traefik.yml up -d | |
| echo " Waiting for Docker network..." | |
| sleep 5 | |
| echo "[9/10] Starting Portainer..." | |
| docker compose -f docker-compose.portainer.yml up -d | |
| echo " Waiting for Portainer to initialize..." | |
| sleep 30 | |
| echo "[9/10] Done" | |
| # ------------------------------------------- | |
| # STEP 10: Initialize Portainer | |
| # ------------------------------------------- | |
| echo "[10/10] Configuring Portainer..." | |
| # Wait for Portainer API to be ready | |
| for i in {1..30}; do | |
| if curl -sk https://localhost:9443/api/status | grep -q "Version"; then | |
| echo " Portainer API is ready" | |
| break | |
| fi | |
| echo " Waiting for Portainer... ($i/30)" | |
| sleep 5 | |
| done | |
| # Create admin user | |
| curl -sk -X POST "https://localhost:9443/api/users/admin/init" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"Username":"admin","Password":"'"$PORTAINER_PASSWORD"'"}' > /dev/null 2>&1 || true | |
| # Authenticate and get JWT | |
| JWT=$(curl -sk -X POST "https://localhost:9443/api/auth" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"Username":"admin","Password":"'"$PORTAINER_PASSWORD"'"}' | jq -r '.jwt') | |
| if [ -z "$JWT" ] || [ "$JWT" = "null" ]; then | |
| echo "[ERROR] Failed to authenticate with Portainer" | |
| echo " Try manually at https://SERVER_IP:9443" | |
| exit 1 | |
| fi | |
| # Create API key for Dublyo control plane | |
| API_KEY=$(curl -sk -X POST "https://localhost:9443/api/users/1/tokens" \ | |
| -H "Authorization: Bearer $JWT" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"description":"Dublyo Control Plane","password":"'"$PORTAINER_PASSWORD"'"}' | jq -r '.rawAPIKey') | |
| # Create local Docker endpoint | |
| ENDPOINT_ID=$(curl -sk -X POST "https://localhost:9443/api/endpoints" \ | |
| -H "X-API-Key: $API_KEY" \ | |
| -F "Name=local" -F "EndpointCreationType=1" | jq -r '.Id') | |
| SERVER_IP=$(curl -s -4 ifconfig.me) | |
| echo "[10/10] Done" | |
| # ------------------------------------------- | |
| # Summary | |
| # ------------------------------------------- | |
| echo "" | |
| echo "========================================" | |
| echo " Setup Complete!" | |
| echo "========================================" | |
| echo "" | |
| echo " Server IP: $SERVER_IP" | |
| echo " Portainer URL: https://$SERVER_IP:9443" | |
| echo " Portainer User: admin" | |
| echo " Portainer Password: $PORTAINER_PASSWORD" | |
| echo " Portainer API Key: $API_KEY" | |
| echo " Endpoint ID: $ENDPOINT_ID" | |
| echo "" | |
| echo " Docker Network: dublyo-public" | |
| echo " Traefik: Running on :80 / :443" | |
| echo " Portainer: Running on :9443" | |
| echo "" | |
| echo " Files:" | |
| echo " $DUBLYO_DIR/docker-compose.traefik.yml" | |
| echo " $DUBLYO_DIR/docker-compose.portainer.yml" | |
| echo " $DUBLYO_DIR/.env (Cloudflare creds)" | |
| echo " $DUBLYO_DIR/certs/ (SSL certs)" | |
| echo " $DUBLYO_DIR/traefik/tls.yml" | |
| echo " $DUBLYO_DIR/acme/acme.json" | |
| echo "" | |
| echo " Firewall (UFW):" | |
| echo " 22/tcp - SSH" | |
| echo " 80/tcp - HTTP (Traefik)" | |
| echo " 443/tcp - HTTPS (Traefik)" | |
| echo " 9443/tcp - Portainer" | |
| echo "" | |
| echo "========================================" | |
| echo " SAVE THESE CREDENTIALS!" | |
| echo "========================================" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment