Add scripts to renew certs with certbot

This commit is contained in:
moeny-matt 2025-04-17 16:04:47 -04:00
parent 707695d631
commit cd24eacee4
4 changed files with 156 additions and 5 deletions

View File

@ -85,11 +85,11 @@ PORTAINER_PASSWORD=
AI_OPENAI_API_KEY=
```
3. Edit GoTrue Dockerfile
3. Edit GoTrue Dockerfile (Not needed on latest version)
Modify the base Dockerfile from the Appflowy-Cloud repo to the one in this repo that installs bash, so the healthcheck can run.
4. Edit nginx.conf
4. Edit nginx.conf (Not needed on latest version)
Modify the base nginx.conf to the version in this repo to resolve a potential websocket DNS resolution issue.
@ -140,13 +140,14 @@ docker-compose up -d
10. Set up auto renewal for the certificates with cron job:
```bash
sudo crontab -e
crontab -e
```
Add this line to run the renewal daily (it will only renew if necessary):
Add this line to run the renewal every month on the 1st day at 2:00 AM:
```bash
0 3 * * * certbot renew --quiet --deploy-hook "docker-compose restart nginx"
0 2 1 * * /home/moeny/AppFlowy-Cloud/check-and-renew-certs.sh >> /var/log/cert-renewal.log 2>&1
```
You will need to add [check-and-renew-certs.sh](check-and-renew-certs.sh) and [renew-certs.sh](renew-certs.sh) in the `AppFlowy-Cloud` directory.
## Additional considerations

49
check-and-renew-certs.sh Normal file
View File

@ -0,0 +1,49 @@
#!/bin/bash
# Run this script with a cron job every month on the 1st day at 2:00 AM
# crontab -e
# 0 2 1 * * /home/moeny/AppFlowy-Cloud/check-and-renew-certs.sh >> /var/log/cert-renewal.log 2>&1
set -e
# Configuration
COMPOSE_FILE="/home/moeny/AppFlowy-Cloud/docker-compose.yml"
DEPLOY_TRIGGER="/etc/letsencrypt/deploy-hook-triggered"
# Ensure we're in the right directory
cd "$(dirname "$COMPOSE_FILE")"
# Set environment variables to ensure non-interactive operation
export DEBIAN_FRONTEND=noninteractive
export PYTHONUNBUFFERED=1
# Run certbot container with explicit non-interactive settings and timeout
echo "[$(date)] Starting certificate renewal check..."
if timeout --signal=KILL 5m docker compose -f "$COMPOSE_FILE" run --rm \
-e TERM=dumb \
-e PYTHONUNBUFFERED=1 \
--no-TTY \
< /dev/null \
certbot; then
echo "[$(date)] Certbot completed successfully"
else
exit_code=$?
if [ $exit_code -eq 137 ]; then # SIGKILL exit code
echo "[$(date)] ERROR: Certbot timed out after 5 minutes, killing container..."
# Find and kill any hanging certbot containers
docker ps --filter "name=certbot" -q | xargs -r docker kill
else
echo "[$(date)] ERROR: Certbot failed with exit code $exit_code"
fi
exit 1
fi
# Check if certificates were renewed
if [ -f "$DEPLOY_TRIGGER" ]; then
echo "[$(date)] Certificates were renewed, restarting nginx..."
docker compose -f "$COMPOSE_FILE" restart nginx
sudo rm "$DEPLOY_TRIGGER"
echo "[$(date)] Nginx restarted successfully"
else
echo "[$(date)] No certificate renewal needed"
fi

View File

@ -219,6 +219,21 @@ services:
environment:
- AF_BASE_URL=${APPFLOWY_BASE_URL:?err}
- AF_GOTRUE_URL=${API_EXTERNAL_URL}
certbot:
image: certbot/certbot
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt
- /home/moeny/AppFlowy-Cloud/renew-certs.sh:/renew-certs.sh
- /home/moeny/keys/tsig.key:/tsig.key:ro
environment:
- BIND_SERVER=ns1.moeny.ai
- DOMAIN=appflowy.moeny.ai
- EMAIL=appflowy-certbot@moeny.ai
- TSIG_KEY=/tsig.key
entrypoint: sh /renew-certs.sh
volumes:
postgres_data:
minio_data:

86
renew-certs.sh Normal file
View File

@ -0,0 +1,86 @@
#!/bin/bash
set -e
# Install required packages
apk add --no-cache bind-tools
# Use environment variables with fallbacks
DOMAIN=${DOMAIN:-"appflowy.moeny.ai"}
EMAIL=${EMAIL:-"appflowy-certbot@moeny.ai"}
BIND_SERVER=${BIND_SERVER:-"ns1.moeny.ai"}
TSIG_KEY=${TSIG_KEY:-"/tsig.key"} # This will be the path inside the container
CERTBOT_DIR="/etc/letsencrypt"
# Create the auth script with embedded functions
cat > /tmp/auth.sh << 'EOF'
#!/bin/sh
# Function to add DNS challenge record
domain="$CERTBOT_DOMAIN"
token="$CERTBOT_VALIDATION"
echo "Adding DNS challenge for $domain with token $token"
nsupdate -k "/tsig.key" << NSUPDATE
server $BIND_SERVER
update add _acme-challenge.$domain. 300 IN TXT "$token"
send
NSUPDATE
# Wait for DNS propagation
echo "Waiting 30 seconds for DNS propagation..."
sleep 30
# Verify the record
echo "Verifying DNS record..."
if dig +short @$BIND_SERVER TXT _acme-challenge.$domain | grep -q "$token"; then
echo "DNS challenge record verified successfully"
exit 0
else
echo "ERROR: DNS challenge record not found or incorrect"
exit 1
fi
EOF
chmod +x /tmp/auth.sh
# Create cleanup script with embedded function
cat > /tmp/cleanup.sh << 'EOF'
#!/bin/sh
domain="$CERTBOT_DOMAIN"
echo "Removing DNS challenge for $domain"
nsupdate -k "/tsig.key" << NSUPDATE
server $BIND_SERVER
update delete _acme-challenge.$domain. TXT
send
NSUPDATE
EOF
chmod +x /tmp/cleanup.sh
# Check if certificate already exists
if [ -d "/etc/letsencrypt/live/$DOMAIN" ]; then
echo "Certificate exists, attempting renewal..."
certbot renew \
--manual \
--preferred-challenges dns \
--manual-auth-hook /tmp/auth.sh \
--manual-cleanup-hook /tmp/cleanup.sh \
--deploy-hook "touch /etc/letsencrypt/deploy-hook-triggered" \
--force-renewal
else
echo "No certificate found, performing initial certificate request..."
certbot certonly \
--manual \
--preferred-challenges dns \
--manual-auth-hook /tmp/auth.sh \
--manual-cleanup-hook /tmp/cleanup.sh \
--deploy-hook "touch /etc/letsencrypt/deploy-hook-triggered" \
-d "$DOMAIN" \
--email "$EMAIL" \
--agree-tos \
--non-interactive
fi
# Clean up temporary scripts
rm -f /tmp/auth.sh /tmp/cleanup.sh
echo "Certificate operation completed. Check /etc/letsencrypt/deploy-hook-triggered for successful deployment."