mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-28 08:49:42 +02:00
feat(scripts): free trusted HTTPS via sslip.io for public-IP remote i… (#460)
* feat(scripts): free trusted HTTPS via sslip.io for public-IP remote installs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: refactor setup scripts * chore: generate sdk * chore: fix messaging for setup_remote script * fix: fix ffmpeg download url * feat: centralise and simplify the url configuration * fix: force script run as sudo * fix: fix documentation --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3309face2c
commit
78427817a6
30 changed files with 838 additions and 392 deletions
|
|
@ -35,9 +35,17 @@ echo "║ Automated HTTPS deployment with TURN server ║"
|
|||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo -e "${NC}"
|
||||
|
||||
# Get the public IP address (skip prompt if SERVER_IP is already set)
|
||||
# This setup must run as root: it provisions Docker, binds privileged ports
|
||||
# 80/443, and (for public IPs) installs a Let's Encrypt certificate plus a
|
||||
# system renewal hook under /etc/letsencrypt — all of which require root. Stop
|
||||
# early with clear guidance rather than getting halfway and degrading the install.
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
dograh_fail "setup_remote.sh must be run as root.\nRe-run with sudo:\n sudo ./setup_remote.sh"
|
||||
fi
|
||||
|
||||
# Get the server IP address (skip prompt if SERVER_IP is already set)
|
||||
if [[ -z "${SERVER_IP:-}" ]]; then
|
||||
echo -e "${YELLOW}Enter your server's public IP address:${NC}"
|
||||
echo -e "${YELLOW}Enter your server's IP address:${NC}"
|
||||
read -p "> " SERVER_IP
|
||||
fi
|
||||
|
||||
|
|
@ -49,6 +57,61 @@ if ! dograh_is_ipv4 "$SERVER_IP"; then
|
|||
dograh_fail "Invalid IP address format"
|
||||
fi
|
||||
|
||||
# Certificate strategy. CERT_MODE selects how HTTPS is secured:
|
||||
# auto - public IP + root + docker -> sslip (trusted); otherwise self-signed
|
||||
# sslip - free trusted Let's Encrypt cert via <ip>.sslip.io (public IP only)
|
||||
# self-signed - generate a self-signed cert (browser shows a warning)
|
||||
# Reserved for future private-network paths (not implemented yet):
|
||||
# letsencrypt-dns, cloudflare-tunnel, external
|
||||
CERT_MODE="${CERT_MODE:-auto}"
|
||||
ACME_DOMAIN_SUFFIX="${ACME_DOMAIN_SUFFIX:-sslip.io}"
|
||||
LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-}"
|
||||
|
||||
if [[ "$CERT_MODE" == "auto" ]]; then
|
||||
if dograh_is_local_ipv4 "$SERVER_IP"; then
|
||||
CERT_MODE="self-signed"
|
||||
dograh_warn "$SERVER_IP is a private IP — using a self-signed certificate."
|
||||
dograh_warn "For a trusted cert, deploy on a public IP or a domain you own"
|
||||
dograh_warn "(https://docs.dograh.com/deployment/custom-domain)."
|
||||
elif ! command -v docker >/dev/null 2>&1; then
|
||||
CERT_MODE="self-signed"
|
||||
dograh_warn "Docker not found — skipping automatic Let's Encrypt setup and using a self-signed cert."
|
||||
else
|
||||
CERT_MODE="sslip"
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$CERT_MODE" in
|
||||
self-signed) ;;
|
||||
sslip)
|
||||
if dograh_is_local_ipv4 "$SERVER_IP"; then
|
||||
dograh_fail "CERT_MODE=sslip needs a public IP; $SERVER_IP is private/reserved."
|
||||
fi
|
||||
command -v docker >/dev/null 2>&1 || dograh_fail "CERT_MODE=sslip needs Docker to serve the ACME challenge."
|
||||
;;
|
||||
letsencrypt-dns|cloudflare-tunnel|external)
|
||||
dograh_fail "CERT_MODE=$CERT_MODE is reserved but not implemented yet. Use 'sslip' (public IP) or 'self-signed'."
|
||||
;;
|
||||
*)
|
||||
dograh_fail "Unknown CERT_MODE '$CERT_MODE' (expected: auto, sslip, self-signed)."
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$CERT_MODE" == "sslip" ]]; then
|
||||
PUBLIC_HOST_VALUE="$(dograh_sslip_host_from_ip "$SERVER_IP" "$ACME_DOMAIN_SUFFIX")"
|
||||
CERT_DESC="Let's Encrypt via $ACME_DOMAIN_SUFFIX (trusted)"
|
||||
else
|
||||
PUBLIC_HOST_VALUE="$SERVER_IP"
|
||||
CERT_DESC="self-signed (browser warning)"
|
||||
fi
|
||||
CERT_RESULT="$CERT_MODE"
|
||||
|
||||
if [[ "$CERT_MODE" == "sslip" && -z "$LETSENCRYPT_EMAIL" && -t 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Email for Let's Encrypt expiry notices (optional, press Enter to skip):${NC}"
|
||||
read -p "> " LETSENCRYPT_EMAIL
|
||||
fi
|
||||
|
||||
FORCE_TURN_RELAY="${FORCE_TURN_RELAY:-false}"
|
||||
|
||||
# Get the TURN secret (skip prompt if TURN_SECRET is already set)
|
||||
|
|
@ -185,6 +248,8 @@ fi
|
|||
echo ""
|
||||
echo -e "${GREEN}Configuration:${NC}"
|
||||
echo -e " Server IP: ${BLUE}$SERVER_IP${NC}"
|
||||
echo -e " Public host: ${BLUE}$PUBLIC_HOST_VALUE${NC}"
|
||||
echo -e " Certificate: ${BLUE}$CERT_DESC${NC}"
|
||||
echo -e " TURN Secret: ${BLUE}********${NC}"
|
||||
echo -e " Deploy mode: ${BLUE}$DEPLOY_MODE${NC}"
|
||||
echo -e " Force TURN relay: ${BLUE}$FORCE_TURN_RELAY${NC}"
|
||||
|
|
@ -240,7 +305,7 @@ openssl req -x509 -nodes -newkey rsa:2048 \\
|
|||
-keyout certs/local.key \\
|
||||
-out certs/local.crt \\
|
||||
-days 365 \\
|
||||
-subj "/CN=$SERVER_IP"
|
||||
-subj "/CN=$PUBLIC_HOST_VALUE"
|
||||
CERT_EOF
|
||||
chmod +x generate_certificate.sh
|
||||
echo -e "${GREEN}✓ generate_certificate.sh created${NC}"
|
||||
|
|
@ -260,19 +325,16 @@ cat > .env << ENV_EOF
|
|||
# Remote deployments run with production signaling and HTTPS defaults
|
||||
ENVIRONMENT=production
|
||||
|
||||
# Canonical public host/base URL for this install.
|
||||
# Canonical public host/base URL for this install. SERVER_IP stays the raw IP
|
||||
# (coturn external-ip and validation need it); PUBLIC_HOST is the sslip.io
|
||||
# hostname when using a trusted cert, otherwise the IP. BACKEND_API_ENDPOINT,
|
||||
# MINIO_PUBLIC_ENDPOINT and TURN_HOST are derived from these by the API
|
||||
# (see api/constants.py) — set them here only to override for a split deployment.
|
||||
SERVER_IP=$SERVER_IP
|
||||
PUBLIC_HOST=$SERVER_IP
|
||||
PUBLIC_BASE_URL=https://$SERVER_IP
|
||||
|
||||
# Backend API endpoint (public URL the backend uses to build webhook/embed links)
|
||||
BACKEND_API_ENDPOINT=https://$SERVER_IP
|
||||
|
||||
# Public URL browsers use to fetch objects from MinIO (proxied by nginx)
|
||||
MINIO_PUBLIC_ENDPOINT=https://$SERVER_IP
|
||||
PUBLIC_HOST=$PUBLIC_HOST_VALUE
|
||||
PUBLIC_BASE_URL=https://$PUBLIC_HOST_VALUE
|
||||
|
||||
# TURN Server Configuration (time-limited credentials via TURN REST API)
|
||||
TURN_HOST=$SERVER_IP
|
||||
TURN_SECRET=$TURN_SECRET
|
||||
# Relay-only ICE candidates for explicit TURN diagnostics
|
||||
FORCE_TURN_RELAY=$FORCE_TURN_RELAY
|
||||
|
|
@ -332,6 +394,46 @@ OVERRIDE_EOF
|
|||
echo -e "${GREEN}✓ docker-compose.override.yaml created${NC}"
|
||||
fi
|
||||
|
||||
if [[ "$CERT_MODE" == "sslip" ]]; then
|
||||
echo ""
|
||||
echo -e "${BLUE}Starting Dograh and requesting a trusted certificate for ${PUBLIC_HOST_VALUE}...${NC}"
|
||||
|
||||
if [[ "$DEPLOY_MODE" == "build" ]]; then
|
||||
./remote_up.sh --build
|
||||
else
|
||||
./remote_up.sh
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}Waiting for nginx to answer on port 80...${NC}"
|
||||
nginx_ready=0
|
||||
for ((i=1; i<=60; i++)); do
|
||||
if curl -s -o /dev/null --max-time 3 "http://127.0.0.1/"; then
|
||||
nginx_ready=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ "$nginx_ready" != "1" ]]; then
|
||||
CERT_RESULT="self-signed"
|
||||
dograh_warn "nginx did not become reachable on port 80 — skipping Let's Encrypt for now."
|
||||
dograh_warn "The stack is running with the bootstrap self-signed certificate."
|
||||
elif dograh_install_certbot && dograh_issue_letsencrypt_webroot "$(pwd)" "$PUBLIC_HOST_VALUE" "$LETSENCRYPT_EMAIL"; then
|
||||
docker compose --profile remote restart nginx >/dev/null 2>&1 || true
|
||||
dograh_install_cert_renewal_hook "$(pwd)" "$PUBLIC_HOST_VALUE"
|
||||
CERT_RESULT="sslip"
|
||||
dograh_success "✓ Trusted Let's Encrypt certificate installed; auto-renewal configured"
|
||||
else
|
||||
CERT_RESULT="self-signed"
|
||||
echo ""
|
||||
dograh_warn "Let's Encrypt issuance failed — the stack is running with the self-signed certificate."
|
||||
dograh_warn "Common causes and fixes:"
|
||||
dograh_warn " - Port 80 not reachable from the internet: open it in your firewall/security group"
|
||||
dograh_warn " - Rate limited on ${ACME_DOMAIN_SUFFIX}: re-run with ACME_DOMAIN_SUFFIX=nip.io"
|
||||
dograh_warn " - Then retry: sudo certbot certonly --webroot -w \"$(pwd)/certs\" -d ${PUBLIC_HOST_VALUE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ Setup Complete! ║${NC}"
|
||||
|
|
@ -350,25 +452,42 @@ echo " - certs/local.crt"
|
|||
echo " - certs/local.key"
|
||||
echo " - .env"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To start Dograh, run:${NC}"
|
||||
echo ""
|
||||
if [[ "$DEPLOY_MODE" != "build" || "${REPO_SOURCE:-}" != "existing" ]]; then
|
||||
echo -e " ${BLUE}cd $(pwd)${NC}"
|
||||
fi
|
||||
if [[ "$DEPLOY_MODE" == "build" ]]; then
|
||||
echo -e " ${BLUE}./remote_up.sh --build${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}A docker-compose.override.yaml has been created alongside${NC}"
|
||||
echo -e "${YELLOW}docker-compose.yaml. Compose auto-loads it, so no -f flag is${NC}"
|
||||
echo -e "${YELLOW}needed — it swaps the prebuilt images for local builds.${NC}"
|
||||
if [[ "$CERT_MODE" == "sslip" ]]; then
|
||||
if [[ "$CERT_RESULT" == "sslip" ]]; then
|
||||
echo -e "${GREEN}Dograh is running with a trusted certificate at:${NC}"
|
||||
echo ""
|
||||
echo -e " ${BLUE}https://$PUBLIC_HOST_VALUE${NC}"
|
||||
echo ""
|
||||
echo -e "${GREEN}No browser warning — the certificate renews automatically before expiry.${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Dograh is running (with a temporary self-signed certificate) at:${NC}"
|
||||
echo ""
|
||||
echo -e " ${BLUE}https://$PUBLIC_HOST_VALUE${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Let's Encrypt issuance did not complete (see the message above). Your${NC}"
|
||||
echo -e "${YELLOW}browser will warn until a trusted certificate is issued.${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${BLUE}./remote_up.sh${NC}"
|
||||
echo -e "${YELLOW}To start Dograh, run:${NC}"
|
||||
echo ""
|
||||
if [[ "$DEPLOY_MODE" != "build" || "${REPO_SOURCE:-}" != "existing" ]]; then
|
||||
echo -e " ${BLUE}cd $(pwd)${NC}"
|
||||
fi
|
||||
if [[ "$DEPLOY_MODE" == "build" ]]; then
|
||||
echo -e " ${BLUE}./remote_up.sh --build${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}A docker-compose.override.yaml has been created alongside${NC}"
|
||||
echo -e "${YELLOW}docker-compose.yaml. Compose auto-loads it, so no -f flag is${NC}"
|
||||
echo -e "${YELLOW}needed — it swaps the prebuilt images for local builds.${NC}"
|
||||
else
|
||||
echo -e " ${BLUE}./remote_up.sh${NC}"
|
||||
fi
|
||||
echo ""
|
||||
echo -e "${YELLOW}Your application will be available at:${NC}"
|
||||
echo ""
|
||||
echo -e " ${BLUE}https://$PUBLIC_HOST_VALUE${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Note:${NC} Your browser will show a security warning for the self-signed"
|
||||
echo "certificate. You can safely accept it to proceed."
|
||||
fi
|
||||
echo ""
|
||||
echo -e "${YELLOW}Your application will be available at:${NC}"
|
||||
echo ""
|
||||
echo -e " ${BLUE}https://$SERVER_IP${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Note:${NC} Your browser will show a security warning for the self-signed"
|
||||
echo "certificate. You can safely accept it to proceed."
|
||||
echo ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue