This repository was archived by the owner on Jul 6, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit-letsencrypt.sh
More file actions
139 lines (124 loc) · 6.65 KB
/
Copy pathinit-letsencrypt.sh
File metadata and controls
139 lines (124 loc) · 6.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/bin/bash
# ─────────────────────────────────────────────────────────────────────────────
# init-letsencrypt.sh
#
# Bootstraps Let's Encrypt certificates for a production Loopless deployment.
# Run this ONCE on a fresh server after DNS is pointed at the host.
#
# Adapted from the canonical wmnnd/nginx-certbot init script pattern.
#
# What it does:
# 1. Verifies DOMAIN and EMAIL env vars are set.
# 2. Downloads Mozilla's recommended TLS parameters (if missing).
# 3. Creates a temporary self-signed cert so Nginx can start without
# a real cert in place (chicken-and-egg: certbot needs Nginx running
# to serve the ACME challenge, but Nginx won't start without certs).
# 4. Starts Nginx with the temporary cert.
# 5. Deletes the temporary cert.
# 6. Runs certbot in --webroot mode to obtain a real cert from Let's Encrypt.
# 7. Reloads Nginx so it picks up the new cert.
#
# Renewal is handled by the long-running certbot service in docker-compose
# (which runs `certbot renew` every 12h) and a cron entry that reloads Nginx
# after successful renewal — see devops/scripts/server/bootstrap-ubuntu.sh.
#
# Usage:
# export DOMAIN=loopless.app
# export EMAIL=ops@loopless.app
# sudo bash devops/scripts/init-letsencrypt.sh
#
# Staging mode (recommended for first run — avoids LE rate limits while testing):
# export STAGING=1
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# ── 1. Validate inputs ───────────────────────────────────────────────────────
if [ -z "${DOMAIN:-}" ]; then
echo -e "${RED}✗ DOMAIN env var is required.${NC}"
echo " Example: export DOMAIN=loopless.app"
exit 1
fi
if [ -z "${EMAIL:-}" ]; then
echo -e "${RED}✗ EMAIL env var is required (Let's Encrypt notifications).${NC}"
echo " Example: export EMAIL=ops@loopless.app"
exit 1
fi
STAGING_FLAG=""
if [ "${STAGING:-0}" = "1" ]; then
STAGING_FLAG="--staging"
echo -e "${YELLOW}⚠ Using Let's Encrypt STAGING environment (untrusted certs).${NC}"
fi
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
COMPOSE="docker compose -f ${REPO_ROOT}/docker-compose.yml"
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ Loopless — Let's Encrypt Bootstrap ║"
echo "╚══════════════════════════════════════════════════╝"
echo ""
echo " Domain: ${DOMAIN}"
echo " Email: ${EMAIL}"
echo " Staging: ${STAGING:-0}"
echo ""
# ── 2. Download recommended TLS parameters ───────────────────────────────────
DATA_PATH="${REPO_ROOT}/devops/nginx/certbot-conf"
mkdir -p "${DATA_PATH}/conf"
if [ ! -e "${DATA_PATH}/conf/options-ssl-nginx.conf" ] || [ ! -e "${DATA_PATH}/conf/ssl-dhparams.pem" ]; then
echo "→ Downloading recommended TLS parameters ..."
curl -fsSL \
https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf \
> "${DATA_PATH}/conf/options-ssl-nginx.conf"
curl -fsSL \
https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem \
> "${DATA_PATH}/conf/ssl-dhparams.pem"
echo -e "${GREEN}✓ TLS parameters downloaded.${NC}"
fi
# ── 3. Create dummy cert so Nginx can start ──────────────────────────────────
echo "→ Creating temporary self-signed cert for ${DOMAIN} ..."
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}"
${COMPOSE} run --rm --entrypoint "\
sh -c 'mkdir -p ${CERT_PATH} && \
openssl req -x509 -nodes -newkey rsa:2048 -days 1 \
-keyout ${CERT_PATH}/privkey.pem \
-out ${CERT_PATH}/fullchain.pem \
-subj /CN=localhost'" certbot
echo -e "${GREEN}✓ Temporary cert in place.${NC}"
# ── 4. Start Nginx with the dummy cert ───────────────────────────────────────
echo "→ Starting Nginx ..."
${COMPOSE} --profile core --profile production-ssl up -d nginx
sleep 3
# ── 5. Delete the dummy cert so certbot doesn't refuse to issue ──────────────
echo "→ Removing temporary cert ..."
${COMPOSE} run --rm --entrypoint "\
sh -c 'rm -rf /etc/letsencrypt/live/${DOMAIN} \
&& rm -rf /etc/letsencrypt/archive/${DOMAIN} \
&& rm -rf /etc/letsencrypt/renewal/${DOMAIN}.conf'" certbot
# ── 6. Request the real cert via webroot ─────────────────────────────────────
echo "→ Requesting Let's Encrypt cert for ${DOMAIN} ..."
${COMPOSE} run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
${STAGING_FLAG} \
--email ${EMAIL} \
-d ${DOMAIN} \
--rsa-key-size 2048 \
--agree-tos \
--force-renewal" certbot
echo -e "${GREEN}✓ Certificate issued.${NC}"
# ── 7. Reload Nginx to pick up the new cert ─────────────────────────────────
echo "→ Reloading Nginx ..."
${COMPOSE} exec nginx nginx -s reload
echo -e "${GREEN}✓ Nginx reloaded.${NC}"
echo ""
echo "╔══════════════════════════════════════════════════╗"
echo "║ Bootstrap complete ║"
echo "╠══════════════════════════════════════════════════╣"
echo "║ Your site is now reachable at: ║"
echo "║ https://${DOMAIN} "
echo "╚══════════════════════════════════════════════════╝"
echo ""
echo " The certbot service auto-renews every 12h."
echo " Add this cron entry to reload Nginx after renewal:"
echo " 0 3 * * * docker compose -f ${REPO_ROOT}/docker-compose.yml kill -s HUP nginx"
echo ""