diff --git a/bonfire_turnkey.sh b/bonfire_turnkey.sh new file mode 100644 index 0000000..aab970a --- /dev/null +++ b/bonfire_turnkey.sh @@ -0,0 +1,316 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================ +# Bonfire Bare-Metal Installer +# Ubuntu 24.04 LTS (Noble) +# ============================ + +echo "=== Bonfire Bare-Metal Installer (Ubuntu 24.04) ===" + +# ---------- prompts ---------- +read -rp "Enter your domain name (FQDN, e.g., example.com): " DOMAIN +while [[ -z "${DOMAIN}" ]]; do + echo "Domain cannot be empty." + read -rp "Enter your domain name: " DOMAIN +done + +read -rp "Contact email for Let's Encrypt (e.g., admin@${DOMAIN}): " EMAIL +while [[ -z "${EMAIL}" ]]; do + echo "Email cannot be empty." + read -rp "Contact email for Let's Encrypt: " EMAIL +done + +read -rp "Bonfire flavour [social|ember|community|cooperation] (default: social): " FLAVOUR +FLAVOUR=${FLAVOUR:-social} + +read -rp "PostgreSQL username (default: bonfire_user): " DB_USER +DB_USER=${DB_USER:-bonfire_user} +DB_NAME="bonfire" + +# we'll generate a random DB password +DB_PASSWORD="$(openssl rand -base64 24)" + +# ---------- FQDN validation ---------- +FQDN_REGEX='^([a-zA-Z0-9](-*[a-zA-Z0-9])*)(\.([a-zA-Z0-9](-*[a-zA-Z0-9])*))*\.[a-zA-Z]{2,}$' +if [[ ! "${DOMAIN}" =~ ${FQDN_REGEX} ]]; then + echo "❌ Invalid FQDN: ${DOMAIN}" + exit 1 +fi + +# ---------- DNS checks (A/AAAA must match this server) ---------- +echo "=== Checking DNS for ${DOMAIN} ===" +# public IPv4 +SERVER_V4="$(curl -4 -fsS https://api.ipify.org || true)" +# public IPv6 (may be empty if host lacks IPv6) +SERVER_V6="$(curl -6 -fsS https://api64.ipify.org || true)" + +# need dig +if ! command -v dig >/dev/null 2>&1; then + echo "Installing dnsutils for DNS checks..." + sudo apt update && sudo apt install -y dnsutils +fi + +DOMAIN_A="$(dig +short A "${DOMAIN}" | head -n1 || true)" +DOMAIN_AAAA="$(dig +short AAAA "${DOMAIN}" | head -n1 || true)" + +echo "Server IPv4: ${SERVER_V4:-}" +echo "Server IPv6: ${SERVER_V6:-}" +echo "Domain A: ${DOMAIN_A:-}" +echo "Domain AAAA: ${DOMAIN_AAAA:-}" + +MISMATCH=true +if [[ -n "${SERVER_V4}" && -n "${DOMAIN_A}" && "${SERVER_V4}" == "${DOMAIN_A}" ]]; then + MISMATCH=false +fi +if [[ -n "${SERVER_V6}" && -n "${DOMAIN_AAAA}" && "${SERVER_V6,,}" == "${DOMAIN_AAAA,,}" ]]; then + MISMATCH=false +fi + +if [[ "${MISMATCH}" == "true" ]]; then + echo "❌ DNS for ${DOMAIN} does not resolve to this server." + echo " Server IPv4: ${SERVER_V4:-}, Domain A: ${DOMAIN_A:-}" + echo " Server IPv6: ${SERVER_V6:-}, Domain AAAA: ${DOMAIN_AAAA:-}" + echo "➡️ Fix your DNS A/AAAA records, wait for propagation, then re-run." + exit 1 +fi + +echo "✅ DNS looks good." + +# ---------- system prep ---------- +echo "=== Updating system packages ===" +sudo apt update && sudo apt -y upgrade + +echo "=== Installing base tools ===" +sudo apt install -y \ + curl git build-essential ca-certificates jq \ + libssl-dev libffi-dev libncurses5-dev \ + libwxgtk3.0-gtk3-dev libgl1-mesa-dev libglu1-mesa-dev \ + libpng-dev libssh-dev unixodbc-dev pkg-config + +echo "=== Installing 'just' command runner ===" +sudo apt install -y just + +echo "=== Installing Node.js 20 + Yarn ===" +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs +npm install -g yarn + +# ---------- PostgreSQL 17 + PostGIS ---------- +echo "=== Installing PostgreSQL 17 + PostGIS ===" +echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt/ noble-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list >/dev/null +curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/pgdg.gpg +sudo apt update +sudo apt install -y postgresql-17 postgresql-17-postgis-3 + +echo "=== Creating PostgreSQL role & database ===" +sudo -u postgres psql -tAc "DO \$\$ BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USER}') THEN + CREATE ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}'; + END IF; +END \$\$;" +sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1 || sudo -u postgres createdb -O "${DB_USER}" "${DB_NAME}" +sudo -u postgres psql -d "${DB_NAME}" -c "CREATE EXTENSION IF NOT EXISTS postgis;" + +# ---------- asdf + mise (match .tool-versions) ---------- +echo "=== Installing asdf (plugins: erlang, elixir) ===" +if [[ ! -d "$HOME/.asdf" ]]; then + git clone https://github.com/asdf-vm/asdf.git "$HOME/.asdf" --branch v0.14.0 +fi +grep -q 'asdf.sh' "$HOME/.bashrc" || echo '. "$HOME/.asdf/asdf.sh"' >> "$HOME/.bashrc" +# shellcheck source=/dev/null +source "$HOME/.asdf/asdf.sh" || true +asdf plugin-add erlang || true +asdf plugin-add elixir || true + +echo "=== Installing mise ===" +curl -fsSL https://mise.jdx.dev/install.sh | sh +grep -q 'mise activate' "$HOME/.bashrc" || echo 'eval "$("$HOME/.local/bin/mise" activate bash)"' >> "$HOME/.bashrc" +eval "$("$HOME/.local/bin/mise" activate bash)" || true + +# ---------- Bonfire clone & tool install ---------- +echo "=== Cloning Bonfire repository ===" +cd "$HOME" +if [[ ! -d "bonfire" ]]; then + git clone --depth 1 https://github.com/bonfire-networks/bonfire-app.git bonfire +fi +cd bonfire + +echo "=== Installing Erlang/Elixir via mise (from .tool-versions) ===" +"$HOME/.local/bin/mise" install + +# ---------- Elixir bug-window check for Pathex ---------- +WITH_PATHEX=1 +if [[ -f ".tool-versions" ]]; then + ELIXIR_LINE="$(grep -E '^elixir[[:space:]]' .tool-versions || true)" + if [[ -n "${ELIXIR_LINE}" ]]; then + ELIXIR_VER="$(echo "${ELIXIR_LINE}" | awk '{print $2}')" + # strip possible 'ref:' etc; keep x.y.z + ELIXIR_VER_CLEAN="$(echo "${ELIXIR_VER}" | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' || true)" + if [[ -n "${ELIXIR_VER_CLEAN}" ]]; then + # compare versions: if >=1.17 and <1.17.3 => WITH_PATHEX=0 + vcmp () { printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1; } + if [[ "$(vcmp "1.17.0" "${ELIXIR_VER_CLEAN}")" == "1.17.0" && "$(vcmp "${ELIXIR_VER_CLEAN}" "1.17.3")" == "${ELIXIR_VER_CLEAN}" && "${ELIXIR_VER_CLEAN}" != "1.17.3" ]]; then + WITH_PATHEX=0 + echo "⚠️ Elixir ${ELIXIR_VER_CLEAN} falls in bug window (>=1.17,<1.17.3). Disabling Pathex (WITH_PATHEX=0)." + fi + fi + fi +fi + +# ---------- environment ---------- +echo "=== Exporting environment ===" +grep -q 'FLAVOUR=' "$HOME/.bashrc" || { + echo "export FLAVOUR=${FLAVOUR}" >> "$HOME/.bashrc" + echo "export MIX_ENV=prod" >> "$HOME/.bashrc" + echo "export WITH_DOCKER=no" >> "$HOME/.bashrc" +} +export FLAVOUR="${FLAVOUR}" +export MIX_ENV=prod +export WITH_DOCKER=no + +echo "=== Initializing config with 'just config' ===" +just config + +echo "=== Writing .env (DB + SECRET_KEY_BASE + WITH_PATHEX=${WITH_PATHEX}) ===" +SECRET_KEY_BASE="$(openssl rand -hex 64)" +cat > .env </dev/null </dev/null < "${HOME}/bonfire/reload-nginx.sh" <<'EOS' +#!/usr/bin/env bash +set -e +sudo nginx -t +sudo systemctl reload nginx +echo "Nginx reloaded." +EOS +chmod +x "${HOME}/bonfire/reload-nginx.sh" + +cat > "${HOME}/bonfire/bonfire-deploy.sh" <<'EOS' +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +echo "Pulling latest code..." +git pull --ff-only + +echo "Ensuring tool versions are installed (mise)..." +~/.local/bin/mise install + +echo "Updating prod config..." +just setup-prod + +echo "Building release..." +just rel-build + +echo "Restarting Bonfire..." +sudo systemctl restart bonfire + +echo "Reloading Nginx..." +./reload-nginx.sh + +echo "Deploy complete." +EOS +chmod +x "${HOME}/bonfire/bonfire-deploy.sh" + +# ---------- done ---------- +echo +echo "=== Bonfire setup complete! 🎉 ===" +echo "Site: https://${DOMAIN}" +echo +echo "Systemd service controls:" +echo " sudo systemctl status bonfire" +echo " sudo systemctl restart bonfire" +echo " sudo systemctl stop bonfire" +echo +echo "Database credentials:" +echo " DB_USER: ${DB_USER}" +echo " DB_PASSWORD: ${DB_PASSWORD}" +echo " DB_NAME: ${DB_NAME}" +echo +echo "Deploy updates with:" +echo " ${HOME}/bonfire/bonfire-deploy.sh" +echo "Reload Nginx with:" +echo " ${HOME}/bonfire/reload-nginx.sh" +echo +echo "Certbot auto-renewal is enabled. To force renew:" +echo " sudo certbot renew && sudo systemctl reload nginx" +