Last active
June 4, 2024 19:10
A script to create a SSL-enabled, custom domain, website with Github pages and Cloudflare.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# ################################################## | |
# Creates a SSL-enabled, custom domain, website with Github Pages and Cloudflare. | |
# | |
# USAGE: ./init-website.sh | |
# AUTHOR: Jon Bake <jonmbake@gmail.com> | |
# LICENSE: MIT | |
# VERSION: 1.0.0 | |
# | |
# ################################################## | |
echo "This script requires a Github Personal Access Token with repo scope (see https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token to create one)..." | |
echo "And a Gloudflare Personal API Token with User:Memberships:Read, Zone:Zone:Edit, Zone:DNS:Edit and Zone:Zone Settings:Edit permissions (see https://developers.cloudflare.com/api/tokens/create). These tokens should be created berfore running this script." | |
read -p "Press enter to continue" | |
# Access tokens can set as an environment variable; if not set, prompt for value | |
if [ -z "$GITHUB_ACCESS_TOKEN" ]; then | |
echo "Github Personal Access Token (with repo scope): "; stty -echo; read GITHUB_ACCESS_TOKEN; stty echo; | |
fi | |
if [ -z "$CLOUDFLARE_ACCESS_TOKEN" ]; then | |
echo "Gloudflare Personal API Token (with Zone:Edit, DNS:Edit and Memberships:Read permissions): "; stty -echo; read CLOUDFLARE_ACCESS_TOKEN; stty echo; | |
fi | |
echo "Name of the github pages repo that you wish to create: "; read GITHUB_REPO_NAME; | |
echo "Custom domain name, e.g. example.com: "; read DOMAIN_NAME; | |
echo 'Github pages folder; must be either `/` or `/docs`.'; read GITHUB_PAGES_FOLDER; | |
echo "Initial markdown content of Github pages index.md, e.g. # My amazing site: "; read GITHUB_INDEX_CONTENT; | |
echo "Creating github repo with name '$GITHUB_REPO_NAME'..." | |
repo_creation_response=$(curl --silent --show-error -X POST \ | |
--header "Authorization: token $GITHUB_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--header "Accept: application/vnd.github.v3+json" \ | |
--data '{ "name" : "'"$GITHUB_REPO_NAME"'" }' \ | |
https://api.github.com/user/repos) | |
echo $repo_creation_response | |
echo "" | |
# Parse out login from response | |
login_username_regex='"login"[[:space:]]*:[[:space:]]*"([0-9a-zA-Z\-]+)"' | |
[[ "$repo_creation_response" =~ $login_username_regex ]] | |
GITHUB_USERNAME="${BASH_REMATCH[1]}" | |
echo "Initializing github pages index.md file..." | |
init_index_response=$(curl --silent --show-error -X PUT \ | |
--header "Authorization: token $GITHUB_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--header "Accept: application/vnd.github.v3+json" \ | |
--data '{ "message" : "Initialize index.md", "content" : "'"$(echo $GITHUB_INDEX_CONTENT | base64)"'" }' \ | |
"https://api.github.com/repos/$GITHUB_USERNAME/$GITHUB_REPO_NAME/contents$([ "$GITHUB_PAGES_FOLDER" == '/' ] && echo "/" || echo "/docs/")index.md") | |
echo $init_index_response | |
echo "" | |
echo "Enabling github pages..." | |
enable_gh_pages_response=$(curl --silent --show-error -X POST \ | |
--header "Authorization: token $GITHUB_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--header "Accept: application/vnd.github.switcheroo-preview+json" \ | |
--data '{ "source": { "branch": "main", "path": "'"$GITHUB_PAGES_FOLDER"'" } }' \ | |
"https://api.github.com/repos/$GITHUB_USERNAME/$GITHUB_REPO_NAME/pages") | |
echo $enable_gh_pages_response | |
echo "" | |
echo "Setting github pages custom domain..." | |
setting_gh_pages_domain_response=$(curl --silent --show-error -X PUT \ | |
--header "Authorization: token $GITHUB_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--header "Accept: application/vnd.github.switcheroo-preview+json" \ | |
--data '{ "cname": "'"$DOMAIN_NAME"'" }' \ | |
"https://api.github.com/repos/$GITHUB_USERNAME/$GITHUB_REPO_NAME/pages") | |
echo $setting_gh_pages_domain_response | |
echo "" | |
echo "Getting cloudflare account id..." | |
cloudflare_membership_response=$(curl --silent --show-error -X GET \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
https://api.cloudflare.com/client/v4/memberships) | |
echo $cloudflare_membership_response | |
echo "" | |
account_id_regex='"account".+"id"[[:space:]]*:[[:space:]]*"([0-9a-zA-Z]+)"' | |
[[ "$cloudflare_membership_response" =~ $account_id_regex ]] | |
CLOUDFLARE_ACCOUNT_ID="${BASH_REMATCH[1]}" | |
echo "Creating cloudflare zone with name '$DOMAIN_NAME'..." | |
cloudflare_create_zone_response=$(curl --silent --show-error -X POST \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--data '{ "name": "'"$DOMAIN_NAME"'", "account" : { "id" : "'"$CLOUDFLARE_ACCOUNT_ID"'" }, "jump_start" : false, "type" : "full" }' \ | |
https://api.cloudflare.com/client/v4/zones) | |
echo $cloudflare_create_zone_response | |
echo "" | |
name_servers_regex='"name_servers"[[:space:]]*:[[:space:]]*\[[[:space:]]*"([0-9a-zA-Z\.]+)"[[:space:]]*,[[:space:]]*"([0-9a-zA-Z\.]+)"[[:space:]]*\]' | |
[[ "$cloudflare_create_zone_response" =~ $name_servers_regex ]] | |
CLOUDFLARE_NAME_SERVER_1="${BASH_REMATCH[1]}" | |
CLOUDFLARE_NAME_SERVER_2="${BASH_REMATCH[2]}" | |
zone_id_regex='"result"[[:space:]]*:[[:space:]]*\{[[:space:]]*"id"[[:space:]]*:[[:space:]]*"([0-9a-zA-Z]+)"' | |
[[ "$cloudflare_create_zone_response" =~ $zone_id_regex ]] | |
CLOUDFLARE_ZONE_ID="${BASH_REMATCH[1]}" | |
echo "Setting cloudflare dns a record pointing to github pages first nameserver..." | |
curl --silent --show-error -X POST \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--data '{ "type": "A", "name": "'"$DOMAIN_NAME"'", "content": "192.30.252.153", "ttl": 1, "proxied": true }' \ | |
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records" | |
echo "" | |
echo "Setting cloudflare dns a record pointing to github pages second nameserver..." | |
curl --silent --show-error -X POST \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--data '{ "type": "A", "name": "'"$DOMAIN_NAME"'", "content": "192.30.252.154", "ttl": 1, "proxied": true }' \ | |
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records" | |
echo "" | |
echo "" | |
echo "*TO FINISH SETUP*: Update the DNS nameservers where $DOMAIN_NAME is registered with the following cloudflare nameservers: $CLOUDFLARE_NAME_SERVER_1, $CLOUDFLARE_NAME_SERVER_2" | |
echo "*IMPORTANT*: Do not press enter until DNS is resolved, i.e. the webpage is accessible at http://$DOMAIN_NAME" | |
read -p "Press enter to continue to https/ssl setup" | |
echo "" | |
echo "Setting cloudflare ssl settings..." | |
curl --silent --show-error -X PATCH \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--data '{ "value": "full" }' \ | |
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/ssl" | |
echo "" | |
echo "Turning on cloudflare automatic HTTPS rewrites..." | |
curl --silent --show-error -X PATCH \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--data '{ "value": "on" }' \ | |
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/automatic_https_rewrites" | |
echo "" | |
echo "Turning on cloudflare always use HTTPS..." | |
curl --silent --show-error -X PATCH \ | |
--header "Authorization: Bearer $CLOUDFLARE_ACCESS_TOKEN" \ | |
--header "Content-Type: application/json" \ | |
--data '{ "enabled": true }' \ | |
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/ssl/universal/settings" | |
echo "" | |
echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is what the required Gloudflare Personal API Token permissions looks like:
This is what the required Github Personal Access Token scope looks like: