Skip to content

Instantly share code, notes, and snippets.

@robertsinfosec
Created January 27, 2025 07:54
Show Gist options
  • Save robertsinfosec/f26bd919ed0c896e55f22befccf0e5d6 to your computer and use it in GitHub Desktop.
Save robertsinfosec/f26bd919ed0c896e55f22befccf0e5d6 to your computer and use it in GitHub Desktop.
Script to easily set up an instance of Supabase with a Reverse Proxy and FQDN.
#!/bin/bash
# Supabase Setup Script
# This script sets up a fresh machine with Supabase by asking configuration questions.
# This script will perform the following actions:
# 1. Install Docker and Docker Compose.
# 2. Create an unprivileged 'supabase' user with appropriate permissions.
# 3. Clone the Supabase repository into /opt/supabase/.
# 4. Configure environment variables with provided values.
# 5. Generate secure JWT keys for Supabase authentication.
# 6. Set up a systemd service to manage Supabase with start/stop capabilities.
# 7. Ensure correct file permissions and ownership.
# 8. Enable and start the Supabase service.
# Define color codes
Black='\033[0;30m'
DarkGray='\033[1;30m'
Red='\033[0;31m'
LightRed='\033[1;31m'
Green='\033[0;32m'
LightGreen='\033[1;32m'
Brown='\033[0;33m'
Yellow='\033[1;33m'
Blue='\033[0;34m'
LightBlue='\033[1;34m'
Purple='\033[0;35m'
LightPurple='\033[1;35m'
Cyan='\033[0;36m'
LightCyan='\033[1;36m'
LightGray='\033[0;37m'
White='\033[1;37m'
NC='\033[0m' # No Color
function setStatus(){
description=$1
severity=$2
case "$severity" in
s)
echo -e "[${LightGreen}+${NC}] ${LightGreen}${description}${NC}"
;;
f)
echo -e "[${LightRed}-${NC}] ${LightRed}${description}${NC}"
;;
w)
echo -e "[${Brown}-${NC}] ${Brown}${description}${NC}"
;;
q)
echo -e "[${LightPurple}?${NC}] ${LightPurple}${description}${NC}"
;;
*)
echo -e "[${LightCyan}*${NC}] ${LightCyan}${description}${NC}"
;;
esac
}
function show_help() {
echo -e "${LightCyan}Supabase Setup Script${NC}"
echo "Usage: $0 [--install|--uninstall|--help]"
echo ""
echo "Options:"
echo " --install Install and configure Supabase."
echo " --uninstall Remove Supabase and all related configurations."
echo " --help Show this help message."
exit 0
}
if [[ $EUID -ne 0 ]]; then
echo -e "${LightRed}[-] This script must be run as the root account or via sudo.${NC}"
exit 1
fi
if [[ "$1" == "--help" ]]; then
show_help
elif [[ "$1" == "--uninstall" ]]; then
setStatus "Enter the FQDN of this host (e.g., supabase.example.com):" "q"
read DOMAIN_NAME
setStatus " - Stopping and disabling the 'supabase' service..." "*"
sudo systemctl stop supabase
sudo systemctl disable supabase
setStatus " - Removing /opt/supabase/ and /etc/systemd/system/supabase.service..." "*"
sudo rm -rf /opt/supabase
sudo rm -f /etc/systemd/system/supabase.service
sudo systemctl daemon-reload
setStatus " - Removing the 'supabase' user..." "*"
sudo userdel -r supabase
setStatus " - Removing Nginx Reverse Proxy..." "*"
rm /etc/nginx/sites-available/$DOMAIN_NAME
rm /etc/nginx/sites-enabled/$DOMAIN_NAME
systemctl restart nginx
setStatus "Supabase successfully uninstalled." "s"
exit 0
elif [[ "$1" != "--install" ]]; then
echo -e "${LightRed}[-] Invalid option. Use --help for usage instructions.${NC}"
exit 1
fi
function generate_random_string() {
openssl rand -base64 32 | tr -d '=+/' | cut -c1-32
}
function generate_jwt_key() {
local ROLE=$1
local IAT=$(date +%s)
local EXP=$((IAT + 157680000)) # 5 years later
echo -n '{"role":"'$ROLE'","iss":"supabase","iat":'$IAT',"exp":'$EXP'}' | openssl enc -base64 -A
}
function validate_passphrase() {
if [[ ${#1} -lt 32 ]]; then
setStatus "Password must be at least 32 characters long." "f"
return 1
fi
return 0
}
setStatus "Starting Supabase setup..." "*"
while true; do
# Prompt for the domain name
setStatus "Enter the FQDN of this host (e.g., supabase.example.com):" "q"
read DOMAIN_NAME
# Prompt for PostgreSQL password
setStatus "Enter your PostgreSQL password or <ENTER> to have one generated:" "q"
read -s POSTGRES_PASSWORD
if [[ "$POSTGRES_PASSWORD" == "" ]]; then
POSTGRES_PASSWORD=$(generate_random_string)
setStatus " - Generated a random PostgreSQL password." "s"
fi
validate_passphrase "$POSTGRES_PASSWORD" || continue
# Prompt for JWT secret
setStatus "Enter your >=32 character JWT secret or <ENTER> to have one generated:" "q"
read -s JWT_SECRET
if [[ "$JWT_SECRET" == "" ]]; then
JWT_SECRET=$(generate_random_string)
setStatus " - Generated a random JWT secret." "s"
fi
validate_passphrase "$JWT_SECRET" || continue
# Show user input and confirm
setStatus "Please review your input:" "*"
echo ""
echo -e "Domain...............: ${LightCyan}$DOMAIN_NAME${NC}"
echo -e "PostgreSQL Password..: ${LightCyan}[HIDDEN]${NC}"
echo -e "JWT Secret...........: ${LightCyan}[HIDDEN]${NC}"
echo ""
echo "This script will perform the following actions:"
echo ""
echo " 1. Install Docker and Docker Compose."
echo " 2. Create an unprivileged 'supabase' user with appropriate permissions."
echo " 3. Clone the Supabase repository into: /opt/supabase/."
echo " 4. Configure environment variables with provided values."
echo " 5. Generate secure JWT keys for Supabase authentication."
echo " 6. Set up a systemd service in /etc/systemd/system/supabase.service to manage Supabase with start/stop capabilities."
echo " 7. Ensure correct file permissions and ownership in /opt/supabase/."
echo " 8. Enable and start the Supabase service."
echo ""
setStatus "WARNING: Is this correct and do you want to continue? (Y to continue, R to restart, any other key to quit)" "q"
read -n 1 -s CONFIRMATION
echo ""
case "$CONFIRMATION" in
[Yy])
break
;;
[Rr])
continue
;;
*)
setStatus "Exiting setup." "f"
exit 1
;;
esac
done
setStatus "Installing required dependencies..." "*"
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release git nginx certbot python3-certbot-nginx
setStatus "Adding Docker GPG key and repository..." "*"
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
setStatus "Installing Docker and Docker Compose..." "*"
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
setStatus " - Done." "s"
setStatus "Creating 'supabase' user..." "*"
if id "supabase" &>/dev/null; then
setStatus " - User 'supabase' already exists." "w"
else
sudo useradd -m -s /bin/bash supabase
sudo usermod -aG docker supabase
echo "supabase ALL=(ALL) NOPASSWD: /bin/systemctl start supabase, /bin/systemctl stop supabase, /bin/systemctl restart supabase" | sudo tee /etc/sudoers.d/supabase
setStatus " - User 'supabase' created and configured." "s"
fi
setStatus "Cloning Supabase repository to /opt/supabase/..." "*"
sudo git clone --depth 1 https://github.com/supabase/supabase /opt/supabase
cp /opt/supabase/docker/.env.example /opt/supabase/docker/.env
setStatus " - Done." "s"
setStatus "Setting folder persmissions and ownership for /opt/supabase/" "*"
sudo chown -R supabase:supabase /opt/supabase
sudo chmod -R 770 /opt/supabase
setStatus " - Done." "s"
setStatus "Setting up systemd service..." "*"
echo "[Unit]
Description=Supabase Service
After=network.target
[Service]
User=supabase
WorkingDirectory=/opt/supabase/docker
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
User=supabase
Restart=always
[Install]
WantedBy=multi-user.target" | sudo tee /etc/systemd/system/supabase.service
setStatus " - Done." "s"
setStatus "Reloading Systemd and setting 'supabase' to start on book, and start now..." "*"
sudo systemctl daemon-reload
sudo systemctl enable supabase
sudo systemctl start supabase
setStatus " - Done." "s"
setStatus "Setting up NGinx Reverse Proxy..." "*"
echo "server {
listen 80
server_name $DOMAIN_NAME;
resolver 127.0.0.53 valid=30s;
set \$upstream_backend http://localhost:8000;
set \$upstream_frontend http://localhost:3000;
location /rest/v1/ {
proxy_pass \$upstream_backend;
}
location /auth/v1/ {
proxy_pass \$upstream_backend;
}
location /realtime/v1/ {
proxy_pass \$upstream_backend;
}
location /storage/v1/ {
proxy_pass \$upstream_backend;
}
location / {
proxy_pass \$upstream_frontend;
}
# Set headers globally for all proxy_pass requests
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}" | sudo tee /etc/nginx/sites-available/$DOMAIN_NAME
ln -s /etc/nginx/sites-available/$DOMAIN_NAME /etc/nginx/sites-enabled/$DOMAIN_NAME
systemctl enable nginx
systemctl start nginx
setStatus " - Done." "s"
setStatus "Generating new ANON and SERVICE keys..." "*"
ANON_KEY=$(generate_jwt_key "anon")
SERVICE_KEY=$(generate_jwt_key "service_role")
setStatus " - Done." "s"
setStatus "Replacing values in the .env file..." "*"
sudo sed -i "s|YOUR_POSTGRES_PASSWORD|$POSTGRES_PASSWORD|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_JWT_SECRET|$JWT_SECRET|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_ANON_KEY|$ANON_KEY|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_SERVICE_KEY|$SERVICE_KEY|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_DOMAIN|$DOMAIN_NAME|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_API_EXTERNAL_URL|https://$DOMAIN_NAME|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_SUPABASE_PUBLIC_URL|https://$DOMAIN_NAME|g" /opt/supabase/docker/.env
sudo sed -i "s|YOUR_SITE_URL|https://$DOMAIN_NAME|g" /opt/supabase/docker/.env
setStatus " - Done." "s"
setStatus "Supabase setup complete. Access it at https://$DOMAIN_NAME" "s"
echo ""
echo "For client access, your keys are below:"
echo ""
echo " - ANON Key......: $ANON_KEY"
echo " - SERVICE Key...: $SERVICE_KEY"
echo ""
echo "These are also available in /opt/supabase/docker/.env"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment