Add installer
This commit is contained in:
parent
f47c4918a6
commit
654f90c384
|
|
@ -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:-<none>}"
|
||||||
|
echo "Server IPv6: ${SERVER_V6:-<none>}"
|
||||||
|
echo "Domain A: ${DOMAIN_A:-<none>}"
|
||||||
|
echo "Domain AAAA: ${DOMAIN_AAAA:-<none>}"
|
||||||
|
|
||||||
|
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:-<none>}, Domain A: ${DOMAIN_A:-<none>}"
|
||||||
|
echo " Server IPv6: ${SERVER_V6:-<none>}, Domain AAAA: ${DOMAIN_AAAA:-<none>}"
|
||||||
|
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 <<EOF
|
||||||
|
DATABASE_URL=ecto://${DB_USER}:${DB_PASSWORD}@localhost/${DB_NAME}
|
||||||
|
SECRET_KEY_BASE=${SECRET_KEY_BASE}
|
||||||
|
WITH_PATHEX=${WITH_PATHEX}
|
||||||
|
# PORT can be set (defaults to 4000)
|
||||||
|
# PORT=4000
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ---------- build release ----------
|
||||||
|
echo "=== Running 'just setup-prod' and 'just rel-build' ==="
|
||||||
|
just setup-prod
|
||||||
|
just rel-build
|
||||||
|
|
||||||
|
# ---------- Nginx + Let's Encrypt ----------
|
||||||
|
echo "=== Installing Nginx + Certbot ==="
|
||||||
|
sudo apt install -y nginx certbot python3-certbot-nginx
|
||||||
|
|
||||||
|
SITE_AVAIL="/etc/nginx/sites-available/bonfire"
|
||||||
|
SITE_LINK="/etc/nginx/sites-enabled/bonfire"
|
||||||
|
APP_UPSTREAM="http://127.0.0.1:4000"
|
||||||
|
|
||||||
|
echo "=== Writing Nginx site config ==="
|
||||||
|
sudo tee "${SITE_AVAIL}" >/dev/null <<NGINX
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ${DOMAIN};
|
||||||
|
|
||||||
|
# Proxy all requests to Phoenix (serves static as well)
|
||||||
|
location / {
|
||||||
|
proxy_pass ${APP_UPSTREAM};
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
|
proxy_set_header Upgrade \$http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
|
||||||
|
sudo ln -sf "${SITE_AVAIL}" "${SITE_LINK}"
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
|
||||||
|
echo "=== Obtaining Let's Encrypt certificate ==="
|
||||||
|
sudo certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos -m "${EMAIL}"
|
||||||
|
|
||||||
|
echo "=== Enabling certbot auto-renew (systemd timer) ==="
|
||||||
|
sudo systemctl enable --now certbot.timer
|
||||||
|
|
||||||
|
# ---------- systemd service ----------
|
||||||
|
echo "=== Creating systemd service 'bonfire.service' ==="
|
||||||
|
SERVICE_FILE="/etc/systemd/system/bonfire.service"
|
||||||
|
RUN_USER="$(id -un)"
|
||||||
|
RUN_GROUP="$(id -gn)"
|
||||||
|
|
||||||
|
sudo tee "${SERVICE_FILE}" >/dev/null <<SYSTEMD
|
||||||
|
[Unit]
|
||||||
|
Description=Bonfire App
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${RUN_USER}
|
||||||
|
Group=${RUN_GROUP}
|
||||||
|
WorkingDirectory=${HOME}/bonfire
|
||||||
|
EnvironmentFile=${HOME}/bonfire/.env
|
||||||
|
ExecStart=${HOME}/bonfire/_build/prod/rel/bonfire/bin/bonfire start daemon
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
SYSTEMD
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now bonfire
|
||||||
|
|
||||||
|
# ---------- helper scripts ----------
|
||||||
|
echo "=== Creating helper scripts ==="
|
||||||
|
cat > "${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"
|
||||||
|
|
||||||
Loading…
Reference in New Issue