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>
This commit is contained in:
Abhishek Kumar 2026-06-21 15:07:18 +05:30
parent 40e34994fd
commit d436fe3b62
6 changed files with 345 additions and 176 deletions

View file

@ -4,8 +4,21 @@ server {
listen 80;
server_name __DOGRAH_PUBLIC_HOST__;
# Redirect all HTTP to HTTPS
return 301 https://$host$request_uri;
# Serve Let's Encrypt HTTP-01 challenges out of the certs webroot that
# certbot --webroot writes into (./certs is bind-mounted here read-only).
# Only this path is exposed; local.crt/local.key are never served.
location ^~ /.well-known/acme-challenge/ {
root /etc/nginx/certs;
default_type "text/plain";
try_files $uri =404;
}
# Redirect everything else to HTTPS. This must live in a location block,
# not a server-level `return`, or it would fire before location matching
# and hijack the ACME challenge above.
location / {
return 301 https://$host$request_uri;
}
}
server {

View file

@ -5,6 +5,10 @@ description: "Deploy Dograh AI with custom domain names and SSL certificates"
Deploy Dograh AI with your own custom domain name for a professional production setup. By now, you should be able to create and test a voice agent by following the previous guide to setup the platform on a remote server using [Docker](docker#option-2%3A-remote-server-deployment)
<Tip>
**You don't need to own a domain just to remove the browser warning.** If your server has a public IP, the [remote setup](docker#option-2%3A-remote-server-deployment) already issues a free, auto-renewing Let's Encrypt certificate via [sslip.io](https://sslip.io) (e.g. `https://203-0-113-10.sslip.io`) — no DNS configuration required. Follow this Custom Domain guide only when you want your **own** domain name (e.g. `voice.yourcompany.com`).
</Tip>
## What is Custom Domain Deployment?
Custom domain deployment allows you to run Dograh AI with a personalized domain name (like `voice.yourcompany.com`) instead of using IP addresses. This setup includes:
@ -109,65 +113,50 @@ sudo apt install certbot -y
sudo yum install certbot -y
```
### Stop Dograh Services
### Point `.env` at your domain
Before generating certificates, stop the running Dograh services to free up port 80:
Update `.env` so the canonical remote settings use your domain — `dograh-init` reads these to render nginx's `server_name`. Replace `voice.yourcompany.com` with your actual domain throughout:
```bash
cd dograh
sudo docker compose --profile remote down
sed -i "s/^PUBLIC_HOST=.*/PUBLIC_HOST=voice.yourcompany.com/" .env
sed -i "s|^PUBLIC_BASE_URL=.*|PUBLIC_BASE_URL=https://voice.yourcompany.com|" .env
```
### Generate SSL Certificates
### Start services so nginx can answer the ACME challenge
Run Certbot to obtain SSL certificates for your domain:
Bring the stack up (or recreate it) through the validated wrapper. nginx serves the Let's Encrypt HTTP-01 challenge from `certs/.well-known/acme-challenge/` on port 80, so the stack must be **running** during issuance — there's no need to stop it:
```bash
sudo certbot certonly --standalone -d voice.yourcompany.com
./remote_up.sh
```
Replace `voice.yourcompany.com` with your actual domain name.
### Generate the SSL certificate (webroot)
Issue the certificate using the webroot challenge served by the running nginx:
```bash
sudo certbot certonly --webroot -w "$(pwd)/certs" -d voice.yourcompany.com
```
Certbot will:
1. Verify that you control the domain
2. Generate SSL certificates
3. Store them in `/etc/letsencrypt/live/voice.yourcompany.com/`
1. Write a challenge file under `certs/.well-known/acme-challenge/`
2. Have Let's Encrypt fetch it over HTTP (port 80) to verify you control the domain
3. Store the certificate in `/etc/letsencrypt/live/voice.yourcompany.com/`
<Note>
You'll be prompted to enter an email address for renewal notifications and agree to the terms of service.
You'll be prompted for an email address (for renewal notices) and to agree to the terms of service. Because nginx keeps serving traffic throughout, issuance and renewal happen with **no downtime** — unlike the older `--standalone` flow, which had to stop the stack to free port 80.
</Note>
### Copy Certificates to Dograh Directory
### Copy the certificate and load it
Copy the generated certificates to the dograh certs directory:
Copy the issued certificate into the `certs/` directory nginx reads, then restart nginx to load it:
```bash
cd dograh
sudo cp /etc/letsencrypt/live/voice.yourcompany.com/fullchain.pem certs/local.crt
sudo cp /etc/letsencrypt/live/voice.yourcompany.com/privkey.pem certs/local.key
sudo chmod 644 certs/local.crt certs/local.key
```
### Update Canonical Public URL Settings
Update `.env` so the canonical remote settings point at your domain:
```bash
nano dograh/.env
```
```bash
PUBLIC_HOST=voice.yourcompany.com
PUBLIC_BASE_URL=https://voice.yourcompany.com
```
### Start Dograh Services
Start Dograh through the validated startup wrapper so `dograh-init` regenerates nginx and coturn runtime config before Docker starts:
```bash
cd dograh
./remote_up.sh
sudo docker compose --profile remote restart nginx
```
### Access Your Application
@ -222,7 +211,7 @@ sudo certbot renew --dry-run
If Certbot fails to generate certificates:
1. **Port 80 blocked**: Ensure port 80 is open in your firewall and no service is using it
1. **Port 80 blocked**: Ensure port 80 is open in your firewall and reachable from the internet. With the webroot flow nginx must be **running** and serving the challenge on port 80 (don't stop the stack)
2. **DNS not propagated**: Wait for DNS changes to propagate and verify with `nslookup`
3. **Rate limits**: Let's Encrypt has rate limits. If you've exceeded them, wait before retrying

View file

@ -123,7 +123,7 @@ Watch the video tutorial below for a step-by-step walkthrough of deploying Dogra
allowFullScreen
></iframe>
Deploy Dograh AI on a remote server to make it accessible from anywhere using your server's IP address. This setup includes HTTPS support via nginx reverse proxy with self-signed certificates. **We need to serve the application over HTTPS, since modern browsers only allow microphone permissions for websites being served over HTTPS**.
Deploy Dograh AI on a remote server to make it accessible from anywhere. If your server has a **public IP**, setup obtains a free, trusted HTTPS certificate automatically via [sslip.io](https://sslip.io) and Let's Encrypt — no domain name or DNS configuration required, and no browser warning. On a private/reserved IP (or when run without root) it falls back to a self-signed certificate. **We need to serve the application over HTTPS, since modern browsers only allow microphone permissions for websites being served over HTTPS**.
<Warning>We highly recommend you set up the platform on a fresh server, so that there are less chances of confliciting dependencies, and ports from other applications.</Warning>
@ -140,9 +140,13 @@ Deploy Dograh AI on a remote server to make it accessible from anywhere using yo
Run the automated setup script that will configure everything for you:
```bash
curl -o setup_remote.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_remote.sh && chmod +x setup_remote.sh && ./setup_remote.sh
curl -o setup_remote.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_remote.sh && chmod +x setup_remote.sh && sudo ./setup_remote.sh
```
<Note>
Run with `sudo` so setup can obtain and auto-renew a Let's Encrypt certificate for a public IP. Without root (or on a private IP) it falls back to a self-signed certificate. Optional environment-variable overrides: `CERT_MODE=self-signed` forces a self-signed cert, `ACME_DOMAIN_SUFFIX=nip.io` switches to the nip.io pool if sslip.io is rate-limited, and `LETSENCRYPT_EMAIL=you@example.com` sets the address for expiry notices.
</Note>
The script will prompt you for:
- Your server's public IP address
- A password for the TURN server (optional, press Enter for default)
@ -152,7 +156,8 @@ The script will prompt you for:
It will automatically:
- Get the source — `docker-compose.yaml` only (prebuilt mode), or clone the full repo (build mode)
- Download the validated remote deployment helper bundle
- Generate SSL certificates
- Obtain a free trusted Let's Encrypt certificate via sslip.io and configure auto-renewal (public IP), or generate a self-signed certificate (private IP / no root)
- For a public IP, also start the stack and print your ready-to-use `https://…sslip.io` URL
- Create an environment file with TURN server configuration
- Validate the runtime config that `dograh-init` will render from `.env`
- Write a `docker-compose.override.yaml` with build directives (build mode only)
@ -163,7 +168,7 @@ It will automatically:
Please ensure that Docker Compose is installed on your machine before proceeding further. You can check whether its installed by running `docker compose version` command. If its not installed, please install it by following your server provider documentation.
</Note>
After the setup script completes, start Dograh. The script prints the exact command to run at the end — it differs slightly between modes:
After the setup script completes, start Dograh. For a **public-IP install the trusted-certificate flow already started the stack** and printed your `https://…sslip.io` URL — you can skip to [Access Your Application](#access-your-application). For a self-signed install, start it yourself (the script prints the exact command at the end — it differs slightly between modes):
<CodeGroup>
```bash Prebuilt mode
@ -180,13 +185,13 @@ First boot in build mode takes several minutes — Docker has to build both the
### Access Your Application
Your application will be available at
```
https://YOUR_SERVER_IP
```
Your application will be available at the URL the setup script printed at the end:
- **Public IP (trusted certificate):** `https://<your-ip-with-dashes>.sslip.io` — for example `https://203-0-113-10.sslip.io`. No browser warning.
- **Self-signed (private IP, or run without `sudo`):** `https://YOUR_SERVER_IP`
<Note>
Since we are using a self-signed certificate, your browser will show a security warning. You can safely accept it to proceed.
With a self-signed certificate your browser shows a security warning you can safely accept. With the sslip.io Let's Encrypt certificate there is no warning.
</Note>
You should be able to create and test a voice agent now.
@ -196,7 +201,7 @@ You should be able to create and test a voice agent now.
- The remote deployment includes an nginx reverse proxy for HTTPS termination
- File downloads (transcripts, recordings) are automatically routed through nginx
- WebSocket connections for real-time features are properly proxied
- The setup uses self-signed certificates - browsers will show a security warning that you can safely accept for testing
- Public-IP installs get a trusted Let's Encrypt certificate (via sslip.io) that renews automatically; private-IP installs use a self-signed certificate (browser warning)
- The TURN server (coturn) is configured for WebRTC NAT traversal
- For production deployments with proper SSL and domain names, see the [Custom Domain](custom-domain) documentation
@ -213,7 +218,7 @@ The setup script creates the following files in the `dograh/` directory:
| `scripts/lib/setup_common.sh` | Shared deployment helper library |
| `deploy/templates/` | nginx and coturn runtime config templates |
| `generate_certificate.sh` | Script to regenerate SSL certificates |
| `certs/local.crt` | Self-signed SSL certificate |
| `certs/local.crt` | SSL certificate (Let's Encrypt via sslip.io, or self-signed) |
| `certs/local.key` | SSL private key |
| `.env` | Single source of truth for deployment settings (TURN secret, JWT secret, FastAPI worker count, public host/base URL) |

View file

@ -410,6 +410,101 @@ dograh_prepare_remote_install() {
dograh_preflight_remote_init_render "$project_dir"
}
# ---------------------------------------------------------------------------
# TLS certificate helpers (self-signed bootstrap + Let's Encrypt via webroot)
# ---------------------------------------------------------------------------
# Map an IPv4 address to a public sslip.io / nip.io hostname, e.g.
# 203.0.113.10 -> 203-0-113-10.sslip.io. The hostname resolves back to the
# embedded IP from any public resolver, so Let's Encrypt can validate it over
# the HTTP-01 challenge without the operator owning a domain. Public IPs only:
# Let's Encrypt refuses to validate private/reserved addresses.
dograh_sslip_host_from_ip() {
local ip=$1
local suffix=${2:-sslip.io}
dograh_is_ipv4 "$ip" || dograh_fail "dograh_sslip_host_from_ip: '$ip' is not an IPv4 address"
printf '%s.%s\n' "${ip//./-}" "$suffix"
}
# Install certbot via the host package manager if it is not already present.
# Returns non-zero (instead of exiting) when no supported package manager is
# found or the install fails, so callers can fall back to a self-signed cert.
dograh_install_certbot() {
if command -v certbot >/dev/null 2>&1; then
return 0
fi
dograh_info "Installing Certbot..."
if command -v apt-get >/dev/null 2>&1; then
apt-get update -qq && apt-get install -y -qq certbot
elif command -v dnf >/dev/null 2>&1; then
dnf install -y -q certbot
elif command -v yum >/dev/null 2>&1; then
yum install -y -q certbot
else
dograh_warn "Could not detect a package manager (apt/dnf/yum) to install certbot."
return 1
fi
}
# Obtain (or renew) a Let's Encrypt certificate for $host using the webroot
# challenge served by the running nginx container out of <project>/certs, then
# copy the issued cert to certs/local.{crt,key} (the files nginx reads). This
# needs nginx already running and serving /.well-known/acme-challenge/ on :80.
# Returns non-zero on failure so callers can keep the self-signed cert.
dograh_issue_letsencrypt_webroot() {
local project_dir=$1
local host=$2
local email=${3:-}
local webroot="$project_dir/certs"
local live_dir="/etc/letsencrypt/live/$host"
local -a email_args
if [[ -n "$email" ]]; then
email_args=(--email "$email")
else
email_args=(--register-unsafely-without-email)
fi
mkdir -p "$webroot/.well-known/acme-challenge"
certbot certonly --webroot -w "$webroot" \
--non-interactive --agree-tos --keep-until-expiring \
"${email_args[@]}" \
-d "$host" || return 1
[[ -f "$live_dir/fullchain.pem" && -f "$live_dir/privkey.pem" ]] || return 1
cp "$live_dir/fullchain.pem" "$webroot/local.crt"
cp "$live_dir/privkey.pem" "$webroot/local.key"
chmod 644 "$webroot/local.crt" "$webroot/local.key"
}
# Install a certbot deploy hook so renewed certificates are copied into
# <project>/certs and nginx is restarted to load them. Renewal itself is driven
# by certbot's packaged systemd timer / cron; webroot renewals need no downtime
# because the running nginx serves the challenge.
dograh_install_cert_renewal_hook() {
local project_dir=$1
local host=$2
local hook_dir="/etc/letsencrypt/renewal-hooks/deploy"
local hook_path="$hook_dir/dograh-reload.sh"
mkdir -p "$hook_dir"
cat > "$hook_path" << HOOK_EOF
#!/bin/bash
cp /etc/letsencrypt/live/$host/fullchain.pem $project_dir/certs/local.crt
cp /etc/letsencrypt/live/$host/privkey.pem $project_dir/certs/local.key
chmod 644 $project_dir/certs/local.crt $project_dir/certs/local.key
cd $project_dir
docker compose --profile remote restart nginx 2>/dev/null || true
HOOK_EOF
chmod +x "$hook_path"
}
dograh_download_bundle_file_for_ref() {
local destination=$1
local remote_path=$2

View file

@ -65,7 +65,7 @@ echo -e " Domain: ${BLUE}$DOMAIN_NAME${NC}"
echo -e " Email: ${BLUE}$EMAIL_ADDRESS${NC}"
echo ""
echo -e "${BLUE}[1/7] Verifying DNS configuration...${NC}"
echo -e "${BLUE}[1/6] Verifying DNS configuration...${NC}"
SERVER_IP="$(curl -s ifconfig.me || curl -s icanhazip.com || echo "")"
RESOLVED_IP="$(dig +short "$DOMAIN_NAME" | tail -1)"
@ -84,22 +84,14 @@ else
echo -e "${GREEN}✓ DNS is correctly configured (${RESOLVED_IP})${NC}"
fi
echo -e "${BLUE}[2/7] Installing Certbot...${NC}"
if command -v apt-get &> /dev/null; then
apt-get update -qq
apt-get install -y -qq certbot
elif command -v yum &> /dev/null; then
yum install -y -q certbot
elif command -v dnf &> /dev/null; then
dnf install -y -q certbot
else
dograh_fail "Could not detect package manager. Please install certbot manually."
fi
echo -e "${BLUE}[2/6] Installing Certbot...${NC}"
dograh_install_certbot || dograh_fail "Could not install certbot. Please install it manually and re-run."
echo -e "${GREEN}✓ Certbot installed${NC}"
echo -e "${BLUE}[3/7] Stopping Dograh services...${NC}"
echo -e "${BLUE}[3/6] Pointing .env at $DOMAIN_NAME and starting services...${NC}"
cd dograh
DOGRAH_DEPLOY_PROJECT_DIR="$(pwd)"
DOGRAH_PATH="$(pwd)"
if [[ ! -f remote_up.sh || ! -f scripts/lib/setup_common.sh ]]; then
dograh_download_remote_support_bundle "$(pwd)" "main"
@ -107,80 +99,10 @@ fi
dograh_require_init_compose_layout "$(pwd)"
if docker compose --profile remote ps --quiet 2>/dev/null | grep -q .; then
docker compose --profile remote down
echo -e "${GREEN}✓ Dograh services stopped${NC}"
else
echo -e "${YELLOW}⚠ No running services found${NC}"
fi
echo -e "${BLUE}[4/7] Generating Let's Encrypt SSL certificate...${NC}"
CERTBOT_OUTPUT=$(certbot certonly --standalone \
--non-interactive \
--agree-tos \
--email "$EMAIL_ADDRESS" \
-d "$DOMAIN_NAME" 2>&1) || {
echo -e "${RED}✗ Certificate generation failed${NC}"
echo ""
if echo "$CERTBOT_OUTPUT" | grep -qi "timeout\|firewall\|connection"; then
echo -e "${YELLOW}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW} Port 80 appears to be blocked by a firewall.${NC}"
echo -e "${YELLOW}═══════════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "Let's Encrypt needs to connect to port 80 to verify domain ownership."
echo ""
elif echo "$CERTBOT_OUTPUT" | grep -qi "too many\|rate.limit"; then
echo -e "${YELLOW}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW} Let's Encrypt rate limit reached.${NC}"
echo -e "${YELLOW}═══════════════════════════════════════════════════════════════${NC}"
echo ""
echo "You've requested too many certificates recently."
echo "Please wait before trying again (usually 1 hour)."
echo ""
elif echo "$CERTBOT_OUTPUT" | grep -qi "dns\|resolve\|NXDOMAIN"; then
echo -e "${YELLOW}═══════════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW} DNS resolution failed.${NC}"
echo -e "${YELLOW}═══════════════════════════════════════════════════════════════${NC}"
echo ""
echo "The domain '$DOMAIN_NAME' does not resolve to this server."
echo "Please verify your DNS A record is correctly configured."
echo ""
else
echo -e "${YELLOW}Certbot output:${NC}"
echo "$CERTBOT_OUTPUT"
echo ""
fi
echo -e "After fixing the issue, re-run this script:"
echo -e " ${BLUE}sudo ./setup_custom_domain.sh${NC}"
echo ""
exit 1
}
echo -e "${GREEN}✓ SSL certificate generated${NC}"
CERT_PATH="/etc/letsencrypt/live/$DOMAIN_NAME"
echo ""
echo -e "${BLUE}Certificate location:${NC}"
echo -e " ${CERT_PATH}/"
[[ -f "$CERT_PATH/fullchain.pem" ]] && echo -e " ${GREEN}${NC} fullchain.pem exists" || echo -e " ${RED}${NC} fullchain.pem NOT FOUND"
[[ -f "$CERT_PATH/privkey.pem" ]] && echo -e " ${GREEN}${NC} privkey.pem exists" || echo -e " ${RED}${NC} privkey.pem NOT FOUND"
echo ""
mkdir -p certs
cp "$CERT_PATH/fullchain.pem" certs/local.crt
cp "$CERT_PATH/privkey.pem" certs/local.key
chmod 644 certs/local.crt certs/local.key
echo -e "${GREEN}${NC} Certificates copied to certs/ directory"
echo ""
echo -e "${BLUE}[5/7] Updating canonical remote settings and validating init-based config...${NC}"
dograh_load_env_file .env
if [[ -z "${SERVER_IP:-}" ]]; then
SERVER_IP="$(dograh_infer_server_ip "$(pwd)" || true)"
fi
[[ -n "${SERVER_IP:-}" ]] || dograh_fail "Could not determine SERVER_IP from the existing install"
dograh_set_env_key .env SERVER_IP "$SERVER_IP"
@ -188,32 +110,56 @@ dograh_set_env_key .env PUBLIC_HOST "$DOMAIN_NAME"
dograh_set_env_key .env PUBLIC_BASE_URL "https://$DOMAIN_NAME"
dograh_delete_env_key .env BACKEND_URL
dograh_prepare_remote_install "$(pwd)"
echo -e "${GREEN}✓ .env synchronized and init-based config validated${NC}"
echo -e "${BLUE}[6/7] Setting up automatic certificate renewal...${NC}"
DOGRAH_PATH="$(pwd)"
# Bring the stack up (recreating it) so dograh-init re-renders nginx with the
# domain server_name and the ACME challenge location, served with the existing
# certificate. certbot --webroot then validates against the running nginx:
# no downtime, and (unlike --standalone) renewal keeps working later while
# nginx holds port 80.
./remote_up.sh
cat > /etc/letsencrypt/renewal-hooks/deploy/dograh-reload.sh << HOOK_EOF
#!/bin/bash
cp /etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem $DOGRAH_PATH/certs/local.crt
cp /etc/letsencrypt/live/$DOMAIN_NAME/privkey.pem $DOGRAH_PATH/certs/local.key
chmod 644 $DOGRAH_PATH/certs/local.crt $DOGRAH_PATH/certs/local.key
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
[[ "$nginx_ready" == "1" ]] || dograh_fail "nginx did not come up on port 80; cannot run the ACME challenge."
echo -e "${GREEN}✓ Services running and serving the ACME challenge${NC}"
cd $DOGRAH_PATH
docker compose --profile remote restart nginx 2>/dev/null || true
HOOK_EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/dograh-reload.sh
echo -e "${BLUE}[4/6] Obtaining Let's Encrypt certificate for $DOMAIN_NAME...${NC}"
if ! dograh_issue_letsencrypt_webroot "$(pwd)" "$DOMAIN_NAME" "$EMAIL_ADDRESS"; then
echo -e "${RED}✗ Certificate issuance failed${NC}"
echo ""
echo -e "${YELLOW}Common causes:${NC}"
echo " - Port 80 not reachable from the internet (open it in your firewall)"
echo " - DNS A record for $DOMAIN_NAME does not point to this server yet"
echo " - Let's Encrypt rate limit reached (wait, then retry)"
echo " - Upgrading an older install: run ./update_remote.sh first to refresh the"
echo " nginx template so it serves the ACME challenge, then re-run this script"
echo ""
echo -e "The stack is still running with the previous certificate."
echo -e "After fixing the issue, re-run: ${BLUE}sudo ./setup_custom_domain.sh${NC}"
echo ""
exit 1
fi
echo -e "${GREEN}✓ Certificate issued and copied to certs/${NC}"
echo -e "${BLUE}[5/6] Loading the new certificate (restarting nginx)...${NC}"
docker compose --profile remote restart nginx >/dev/null 2>&1 || true
echo -e "${GREEN}✓ nginx restarted${NC}"
echo -e "${BLUE}[6/6] Configuring automatic certificate renewal...${NC}"
dograh_install_cert_renewal_hook "$(pwd)" "$DOMAIN_NAME"
if certbot renew --dry-run --quiet; then
echo -e "${GREEN}✓ Auto-renewal configured and tested${NC}"
else
echo -e "${YELLOW}⚠ Auto-renewal test had issues, but certificates are installed${NC}"
echo -e "${YELLOW}⚠ Auto-renewal dry-run had issues, but the certificate is installed${NC}"
fi
echo ""
echo -e "${BLUE}[7/7] Starting Dograh services through validated startup wrapper...${NC}"
./remote_up.sh
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ Custom Domain Setup Complete! ║${NC}"

View file

@ -49,6 +49,66 @@ 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 $SERVER_IP is a private/reserved address — Let's Encrypt cannot validate it."
dograh_warn "Falling back to a self-signed certificate. For a trusted cert deploy on a public IP,"
dograh_warn "or use a domain you own (https://docs.dograh.com/deployment/custom-domain)."
elif [[ $EUID -ne 0 ]]; then
CERT_MODE="self-signed"
dograh_warn "Not running as root — skipping automatic Let's Encrypt setup."
dograh_warn "Re-run with sudo (sudo ./setup_remote.sh) for a free trusted certificate via sslip.io."
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
[[ $EUID -eq 0 ]] || dograh_fail "CERT_MODE=sslip needs root for certbot. Re-run with sudo."
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 +245,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 +302,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 +322,21 @@ 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.
SERVER_IP=$SERVER_IP
PUBLIC_HOST=$SERVER_IP
PUBLIC_BASE_URL=https://$SERVER_IP
PUBLIC_HOST=$PUBLIC_HOST_VALUE
PUBLIC_BASE_URL=https://$PUBLIC_HOST_VALUE
# Backend API endpoint (public URL the backend uses to build webhook/embed links)
BACKEND_API_ENDPOINT=https://$SERVER_IP
BACKEND_API_ENDPOINT=https://$PUBLIC_HOST_VALUE
# Public URL browsers use to fetch objects from MinIO (proxied by nginx)
MINIO_PUBLIC_ENDPOINT=https://$SERVER_IP
MINIO_PUBLIC_ENDPOINT=https://$PUBLIC_HOST_VALUE
# TURN Server Configuration (time-limited credentials via TURN REST API)
TURN_HOST=$SERVER_IP
TURN_HOST=$PUBLIC_HOST_VALUE
TURN_SECRET=$TURN_SECRET
# Relay-only ICE candidates for explicit TURN diagnostics
FORCE_TURN_RELAY=$FORCE_TURN_RELAY
@ -332,6 +396,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 +454,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 ""