From 734638870bd7dc9f65490f1e09ed8febd9425442 Mon Sep 17 00:00:00 2001 From: "Mehdi (OSX)" Date: Wed, 22 Nov 2023 15:17:13 +0500 Subject: [PATCH] First commit. --- .gitattributes | 208 ++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 2 + .idea/.gitignore | 8 ++ .idea/modules.xml | 8 ++ .idea/php.xml | 19 ++++ .idea/untitled1.iml | 8 ++ .idea/vcs.xml | 6 ++ README.md | 57 ++++++++++++ certs/.gitkeep | 0 generate-certs.sh | 115 ++++++++++++++++++++++++ 10 files changed, 431 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/php.xml create mode 100644 .idea/untitled1.iml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 certs/.gitkeep create mode 100755 generate-certs.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7b431d8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,208 @@ +# Taken from: https://github.com/alexkaratarakis/gitattributes/blob/master/Web.gitattributes +## GITATTRIBUTES FOR WEB PROJECTS +# +# These settings are for any web project. +# +# Details per file setting: +# text These files should be normalized (i.e. convert CRLF to LF). +# binary These files are binary and should be left untouched. +# +# Note that binary is a macro for -text -diff. +###################################################################### + +# Auto detect +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +* text=auto + +# Source code +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.coffee text +*.css text diff=css +*.htm text diff=html +*.html text diff=html +*.inc text +*.ini text +*.js text +*.json text +*.jsx text +*.less text +*.ls text +*.map text -diff +*.od text +*.onlydata text +*.php text diff=php +*.pl text +*.ps1 text eol=crlf +*.py text diff=python +*.rb text diff=ruby +*.sass text +*.scm text +*.scss text diff=css +*.sh text eol=lf +.husky/* text eol=lf +*.sql text +*.styl text +*.tag text +*.ts text +*.tsx text +*.xml text +*.xhtml text diff=html + +# Docker +Dockerfile text + +# Documentation +*.ipynb text eol=lf +*.markdown text diff=markdown +*.md text diff=markdown +*.mdwn text diff=markdown +*.mdown text diff=markdown +*.mkd text diff=markdown +*.mkdn text diff=markdown +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +# Templates +*.dot text +*.ejs text +*.erb text +*.haml text +*.handlebars text +*.hbs text +*.hbt text +*.jade text +*.latte text +*.mustache text +*.njk text +*.phtml text +*.svelte text +*.tmpl text +*.tpl text +*.twig text +*.vue text + +# Configs +*.cnf text +*.conf text +*.config text +.editorconfig text +.env text +.gitattributes text +.gitconfig text +.htaccess text +*.lock text -diff +package.json text eol=lf +package-lock.json text eol=lf -diff +pnpm-lock.yaml text eol=lf -diff +.prettierrc text +yarn.lock text -diff +*.toml text +*.yaml text +*.yml text +browserslist text +Makefile text +makefile text + +# Heroku +Procfile text + +# Graphics +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.gifv binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +# SVG treated as an asset (binary) by default. +*.svg text +# If you want to treat it as binary, +# use the following line instead. +# *.svg binary +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + +# Audio +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +# Video +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.avi binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.ogv binary +*.swc binary +*.swf binary +*.webm binary + +# Archives +*.7z binary +*.gz binary +*.jar binary +*.rar binary +*.tar binary +*.zip binary + +# Fonts +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary + +# Executables +*.exe binary +*.pyc binary + +# RC files (like .babelrc or .eslintrc) +*.*rc text + +# Ignore files (like .npmignore or .gitignore) +*.*ignore text \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..991291a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/certs/** +!/certs/.gitkeep \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4922bfe --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..f324872 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/untitled1.iml b/.idea/untitled1.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/untitled1.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..db2959d --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +**Check config options in** `generate-certs.sh` + +--- + +- It uses certbot's docker to generate LetsEncrypt SSL certificates, and it comes with simple script to generate and renew certificate for **single domain.** +- It **does not support multiple domains**. But you can create certificate for test.com, abc.test.com, *.test.com (wildcard). + +--- + +## Info: +- This script will **create** SSL certificates based on `${new_ssl_command}`. +- If the `${live_certs_dir}` and `${live_certs_dir}/cert.pem` exist then certificate is **renewed** based on `${renew_command}` +- Post hook is only executed: + 1. If a new ssl certificate is created. + 2. If certificate is renewed (test is done by comparing ${live_certs_dir}/cert.pem modified time to last one). + +--- + +## Usage: + +- Set configuration in `generate-certs.sh` +- Set executable permission: `chmod +x generate-certs.sh` +- Run: `generate-certs.sh` +- After the certificates are created: + - Make sure to mount both `certs/test.com/live` and `certs/test.com/archive` directory, and use `live/*.pem` certificates in your nginx config. + - Because archive directory has actual files, but live directory has symlink to archive. In archive, certs are stored like this fullchain1.pem, cert1.pem and number is increased based on renewals. But live folder has direct certificates without number like cert.pem, fullchain.pem +- Also use: https://github.com/certbot/certbot/blob/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf + +--- + +## Example nginx config: +``` +server { + listen 443; + server_name test.com; + + #SSL - Set in production. + ssl_certificate /etc/letsencrypt/live/test.com/fullchain.pem; #managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/test.com/privkey.pem; #managed by Certbot + include /etc/nginx/commons/letsencrypt-options-ssl-nginx.conf; +} +``` + +## Important: +- **In case of errors, try clearing certs/\* folder and retry.** _(Although keep .gitkeep file.)_ +- Use `dry_run=true` to test certbot instead of actually creating/renewing certificates. + +--- + +## Cron: +- Run this file under cron to keep the certificates updated. +- Certbot generate certificates with expiry of 90days, so run this script every day once. +- If a certificate is renewed (based on change of last-modified time, to existing certificate) then `posthook` is executed. +- You can restart nginx in `posthook`, to reload used certificates. +- Example: + - `nano /etc/cron.d/generate-certs-for-test.com` + - `0 1 * * * root /script-location/generate-certs.sh > /script-location/generate-certs.log` diff --git a/certs/.gitkeep b/certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/generate-certs.sh b/generate-certs.sh new file mode 100755 index 0000000..91108f0 --- /dev/null +++ b/generate-certs.sh @@ -0,0 +1,115 @@ +#!/bin/bash + + +#CONFIG: +dry_run=true + +cloudflare_token='' #Create it from Cloudflare, and limit it the DNZ zone of your domain. + +live_certs_dir="certs/live/calm.biz"; #Script will check this directory for empty, to determine to create or renew ssl certificates. + +new_ssl_command="docker run --rm -v ./certs:/etc/letsencrypt -v #cloudflare_token_file#:/certbot-cloudflare certbot/dns-cloudflare certonly #dry_run_arg# --dns-cloudflare --dns-cloudflare-credentials /certbot-cloudflare -d calm.biz -d \*.calm.biz -d temp.temp.calm.biz --preferred-challenges dns-01 --preferred-chain 'ISRG Root X1' --non-interactive --dns-cloudflare-propagation-seconds 20 --agree-tos --email w3goodies.com@gmail.com"; +renew_command="docker run --rm -v ./certs:/etc/letsencrypt -v #cloudflare_token_file#:/certbot-cloudflare certbot/dns-cloudflare renew #dry_run_arg# --non-interactive --agree-tos --email w3goodies.com@gmail.com --no-random-sleep-on-renew" + +post_hook="docker-compose restart nginx" #posthook is executed if certificate is created for first time, or if "${live_certs_dir}/cert.pem" file is modified (based on checking last modified time). + +#END CONFIG + +#chdir to current dir. +cd "$(dirname "$0")" + +#remove trailing slash from live_certs_dir +live_certs_dir=${live_certs_dir%/} + +separator="==========" + +#Set cloudflare token in file because certbot requires it inside a file. +cloudflare_token_file="./cf-tmp" +echo "dns_cloudflare_api_token = ${cloudflare_token}" > ${cloudflare_token_file} +chmod 600 ${cloudflare_token_file} +new_ssl_command=${new_ssl_command//#cloudflare_token_file#/$cloudflare_token_file} +renew_command=${renew_command//#cloudflare_token_file#/$cloudflare_token_file} + +#Set dry run flag in command if true. +dry_run_arg="" +if [ "$dry_run" = true ] ; then + dry_run_arg=" --dry-run" +fi +new_ssl_command=${new_ssl_command//#dry_run_arg#/$dry_run_arg} +renew_command=${renew_command//#dry_run_arg#/$dry_run_arg} + +echo "" + +#Check if certificate exist +live_cert_file="${live_certs_dir}/cert.pem" +if [ -d "$live_certs_dir" ] && [ -f "$live_cert_file" ] +then + #Renew + last_modified_time=$(date -r "${live_cert_file}") + + echo "${separator}" + echo "Certificates folder exist: ${live_certs_dir}" + echo "TRYING TO RENEW CERTIFICATES..." + echo "${separator}" + if [ "$dry_run" = true ] ; then + echo -e "\n${separator}\n[DRY-RUN ENABLED]\n${separator}\n" + else + echo -e "\n${separator}\n" + fi + echo -e "Output from renew command:\n" + + eval "${renew_command}" + + if [ $? -eq 0 ]; then + echo -e "${separator}\nCommand exited successfully.\n${separator}\n" + + #Check if file is modified. + new_modified_time=$(date -r "${live_cert_file}") + if [ "$last_modified_time" != "$new_modified_time" ]; then + echo -e "${separator}\nChange found in: ${live_cert_file}, therefore executing posthook.\n${separator}\n"; + + eval "${post_hook}" + + if [ $? -eq 0 ]; then + echo -e "\n${separator}\nPost hook successfully executed.\n${separator}\n" + else + echo -e "\n${separator}\nERROR! Unable to execute post hook.\n${separator}" + fi + else + echo -e "${separator}\nNo change in certificate so posthook is ignored.\n${separator}" + fi + else + echo -e "\n${separator}\nERROR! Exiting.\n${separator}" + fi +else + #Create + echo "${separator}" + echo "Certificates folder does not exist: ${live_certs_dir}" + echo "TRYING TO CREATE SSL CERTIFICATES..." + if [ "$dry_run" = true ] ; then + echo -e "\n${separator}\n[DRY-RUN ENABLED]\n${separator}\n" + else + echo -e "\n${separator}\n" + fi + echo -e "Output from new ssl command:\n" + + eval "${new_ssl_command}" + + if [ $? -eq 0 ]; then + echo -e "\n${separator}\nCommand exited successfully therefore executing posthook.\n${separator}\n" + eval "${post_hook}" + + if [ $? -eq 0 ]; then + echo "Post hook successfully executed." + else + echo -e "\n${separator}\nERROR! Unable to execute post hook.\n${separator}" + fi + else + echo -e "\n${separator}\nERROR! Exiting.\n${separator}" + fi +fi + +echo "" + +#Remove tmp file +rm -f ${cloudflare_token_file} \ No newline at end of file