Changing approach
This commit is contained in:
parent
7325976ebd
commit
4fa50fd49e
|
|
@ -1,207 +1,421 @@
|
|||
#!/usr/bin/env bash
|
||||
# bonfire_turnkey_asdf_fixed.sh
|
||||
# All-in-one Bonfire installer using ASDF-managed Erlang/Elixir/Node (fixed: early curl/gnupg + pgdg key)
|
||||
# Target: Ubuntu 24.04 / Debian 12+
|
||||
#
|
||||
# Run as root:
|
||||
# sudo bash ./bonfire_turnkey_asdf_fixed.sh
|
||||
#
|
||||
# Notes:
|
||||
# - Building Erlang/Elixir with asdf may take a long time on first run.
|
||||
# - By default the script checks DNS A/AAAA for the provided domain; use --skip-dns-check to bypass.
|
||||
# - This script attempts to be idempotent where reasonable.
|
||||
set -euo pipefail
|
||||
|
||||
# ============================
|
||||
# Bonfire Bare-Metal Installer
|
||||
# Ubuntu 24.04 LTS (Noble)
|
||||
# ============================
|
||||
### ---------------------------
|
||||
### Configuration defaults
|
||||
### ---------------------------
|
||||
APP_USER="bonfire"
|
||||
APP_HOME="/opt/${APP_USER}"
|
||||
APP_DIR="${APP_HOME}/app"
|
||||
APP_PORT="4000"
|
||||
PG_DB="bonfire"
|
||||
PG_USER_DEFAULT="bonfire_user"
|
||||
PG_VER_PREFERRED="17"
|
||||
ASDF_VERSION="v0.14.0"
|
||||
KERL_CONFIGURE_OPTIONS="--without-javac --without-wx"
|
||||
SKIP_DNS_CHECK=0
|
||||
|
||||
echo "=== Bonfire Bare-Metal Installer (Ubuntu 24.04) ==="
|
||||
### ---------------------------
|
||||
### Helpers
|
||||
### ---------------------------
|
||||
log(){ printf "\n\033[1;36m==> %s\033[0m\n" "$*"; }
|
||||
info(){ printf "\033[1;32m[info]\033[0m %s\n" "$*"; }
|
||||
warn(){ printf "\033[1;33m[warn]\033[0m %s\n" "$*"; }
|
||||
err(){ printf "\033[1;31m[error]\033[0m %s\n" "$*"; }
|
||||
die(){ err "$*"; exit 1; }
|
||||
|
||||
# ---------- 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
|
||||
require_root(){
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
die "This script must be run as root (sudo)."
|
||||
fi
|
||||
}
|
||||
require_root
|
||||
|
||||
### ---------------------------
|
||||
### CLI args
|
||||
### ---------------------------
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--skip-dns-check) SKIP_DNS_CHECK=1; shift;;
|
||||
*) die "Unknown argument: $1";;
|
||||
esac
|
||||
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
|
||||
### ---------------------------
|
||||
### Interactive prompts
|
||||
### ---------------------------
|
||||
read -rp "Enter your domain (FQDN, e.g. bonfire.example.com): " DOMAIN
|
||||
while [[ -z "$DOMAIN" ]]; do read -rp "Domain cannot be empty. Enter domain: " DOMAIN; done
|
||||
|
||||
read -rp "Contact email for Let's Encrypt (admin@${DOMAIN}): " EMAIL
|
||||
while [[ -z "$EMAIL" ]]; do read -rp "Email cannot be empty. Enter contact email: " 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"
|
||||
read -rp "PostgreSQL role to create (default: ${PG_USER_DEFAULT}): " PG_USER
|
||||
PG_USER=${PG_USER:-$PG_USER_DEFAULT}
|
||||
PG_PASS="$(openssl rand -base64 24)"
|
||||
|
||||
# we'll generate a random DB password
|
||||
DB_PASSWORD="$(openssl rand -base64 24)"
|
||||
log "Inputs summary:"
|
||||
info "Domain: ${DOMAIN}"
|
||||
info "Email: ${EMAIL}"
|
||||
info "Flavour: ${FLAVOUR}"
|
||||
info "Postgres role: ${PG_USER} (password auto-generated)"
|
||||
|
||||
# ---------- FQDN validation ----------
|
||||
### ---------------------------
|
||||
### Validate FQDN (basic)
|
||||
### ---------------------------
|
||||
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
|
||||
if [[ ! "$DOMAIN" =~ $FQDN_REGEX ]]; then
|
||||
die "Invalid FQDN: $DOMAIN"
|
||||
fi
|
||||
|
||||
# ---------- DNS checks (A/AAAA must match this server) ----------
|
||||
echo "=== Checking DNS for ${DOMAIN} ==="
|
||||
# public IPv4
|
||||
### ---------------------------
|
||||
### System update & ensure curl/gnupg early
|
||||
### ---------------------------
|
||||
log "APT update & ensure curl/gnupg are present (required early for repos)"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -y
|
||||
apt-get upgrade -y
|
||||
|
||||
# Ensure curl / gnupg are present early for repo keys
|
||||
apt-get install -y --no-install-recommends curl wget gnupg gpg-agent dirmngr lsb-release apt-transport-https ca-certificates || true
|
||||
|
||||
### ---------------------------
|
||||
### Install build deps (may include some heavy packages needed for asdf builds)
|
||||
### ---------------------------
|
||||
log "Installing build dependencies (Erlang/Elixir builds need several packages)"
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential automake autoconf m4 libncurses5-dev libncursesw5-dev \
|
||||
libssl-dev libreadline-dev zlib1g-dev pkg-config unzip \
|
||||
libxml2-utils xsltproc fop libxslt1.1 libxslt1-dev \
|
||||
libwxgtk-webview3.2-dev libglu1-mesa-dev libgl1-mesa-dev \
|
||||
libpng-dev libssh-dev unixodbc-dev default-jdk rsync jq dnsutils git || true
|
||||
|
||||
# optional: try to install just if in apt repos
|
||||
if ! command -v just >/dev/null 2>&1; then
|
||||
if apt-cache show just >/dev/null 2>&1; then
|
||||
apt-get install -y just || true
|
||||
fi
|
||||
fi
|
||||
|
||||
### ---------------------------
|
||||
### PostgreSQL repository key setup (PGDG) - idempotent
|
||||
### ---------------------------
|
||||
# -------------------------
|
||||
# PGDG key + repo (idempotent, modern Ubuntu/Debian)
|
||||
# -------------------------
|
||||
PG_KEY_URL="https://www.postgresql.org/media/keys/ACCC4CF8.asc"
|
||||
TRUSTED_KEY="/etc/apt/trusted.gpg.d/postgresql.gpg"
|
||||
CODENAME="$(lsb_release -cs || echo noble)"
|
||||
LIST_FILE="/etc/apt/sources.list.d/pgdg.list"
|
||||
|
||||
# ensure curl & gpg are present
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends curl gnupg lsb-release apt-transport-https ca-certificates || true
|
||||
|
||||
# import/dearmor the key if missing
|
||||
if [[ ! -f "${TRUSTED_KEY}" ]]; then
|
||||
info "Importing PostgreSQL signing key to ${TRUSTED_KEY}..."
|
||||
curl -fsSL "${PG_KEY_URL}" | gpg --dearmor -o "${TRUSTED_KEY}" \
|
||||
|| { rm -f "${TRUSTED_KEY}"; die "Failed to fetch/dearmor PGDG key"; }
|
||||
chmod 644 "${TRUSTED_KEY}"
|
||||
chown root:root "${TRUSTED_KEY}"
|
||||
else
|
||||
info "PGDG key already present at ${TRUSTED_KEY}; skipping import."
|
||||
fi
|
||||
|
||||
# write the repo list (idempotent)
|
||||
echo "deb http://apt.postgresql.org/pub/repos/apt ${CODENAME}-pgdg main" > "${LIST_FILE}"
|
||||
chmod 644 "${LIST_FILE}"
|
||||
info "PGDG APT entry written to ${LIST_FILE}"
|
||||
|
||||
# update package lists
|
||||
apt-get update -y || die "apt-get update failed after adding PGDG repo"
|
||||
# -------------------------
|
||||
|
||||
### ---------------------------
|
||||
### Install PostgreSQL + PostGIS
|
||||
### ---------------------------
|
||||
log "Installing PostgreSQL and PostGIS (attempting preferred ${PG_VER_PREFERRED})"
|
||||
if apt-cache policy "postgresql-${PG_VER_PREFERRED}" | grep -q Candidate; then
|
||||
apt-get install -y "postgresql-${PG_VER_PREFERRED}" "postgresql-${PG_VER_PREFERRED}-postgis-3" postgis || true
|
||||
else
|
||||
apt-get install -y postgresql postgis postgresql-contrib || true
|
||||
fi
|
||||
systemctl enable --now postgresql || true
|
||||
|
||||
### ---------------------------
|
||||
### Set locale to UTF-8 (avoid Elixir latin1 warning)
|
||||
### ---------------------------
|
||||
log "Ensuring UTF-8 locale"
|
||||
apt-get install -y locales || true
|
||||
if ! grep -q "en_US.UTF-8 UTF-8" /etc/locale.gen 2>/dev/null; then
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
|
||||
fi
|
||||
locale-gen en_US.UTF-8 >/dev/null || true
|
||||
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||
export ELIXIR_ERL_OPTIONS="+fnu"
|
||||
|
||||
### ---------------------------
|
||||
### Create bonfire user and set ownership
|
||||
### ---------------------------
|
||||
log "Creating system user '${APP_USER}' and preparing ${APP_HOME}"
|
||||
if id -u "${APP_USER}" >/dev/null 2>&1; then
|
||||
info "User ${APP_USER} already exists"
|
||||
else
|
||||
adduser --system --group --home "${APP_HOME}" --shell /bin/bash "${APP_USER}" || true
|
||||
fi
|
||||
mkdir -p "${APP_HOME}"
|
||||
chown -R "${APP_USER}:${APP_USER}" "${APP_HOME}"
|
||||
chmod 750 "${APP_HOME}"
|
||||
|
||||
# limited sudo for helper scripts (reload nginx, systemctl)
|
||||
echo "${APP_USER} ALL=(ALL) NOPASSWD:/usr/sbin/nginx,/bin/systemctl" > "/etc/sudoers.d/${APP_USER}"
|
||||
chmod 0440 "/etc/sudoers.d/${APP_USER}"
|
||||
|
||||
### ---------------------------
|
||||
### Install ASDF for bonfire user
|
||||
### ---------------------------
|
||||
log "Installing asdf for ${APP_USER} if missing"
|
||||
if [[ ! -d "${APP_HOME}/.asdf" ]]; then
|
||||
sudo -u "${APP_USER}" bash -lc "git clone https://github.com/asdf-vm/asdf.git ${APP_HOME}/.asdf --branch ${ASDF_VERSION}"
|
||||
fi
|
||||
|
||||
# ensure .bashrc loads asdf
|
||||
if ! sudo -u "${APP_USER}" bash -lc "grep -q 'asdf.sh' ${APP_HOME}/.bashrc" >/dev/null 2>&1; then
|
||||
cat >> "${APP_HOME}/.bashrc" <<'ASDFRC'
|
||||
# ASDF
|
||||
. "$HOME/.asdf/asdf.sh"
|
||||
. "$HOME/.asdf/completions/asdf.bash"
|
||||
ASDFRC
|
||||
chown "${APP_USER}:${APP_USER}" "${APP_HOME}/.bashrc"
|
||||
fi
|
||||
|
||||
# create local bin dir (nodejs plugin helper uses it)
|
||||
sudo -u "${APP_USER}" bash -lc "mkdir -p ${APP_HOME}/.local/bin && chmod 755 ${APP_HOME}/.local/bin"
|
||||
|
||||
### ---------------------------
|
||||
### Clone Bonfire repository
|
||||
### ---------------------------
|
||||
log "Cloning Bonfire repository into ${APP_DIR}"
|
||||
if [[ ! -d "${APP_DIR}" ]]; then
|
||||
sudo -u "${APP_USER}" git clone --depth 1 https://github.com/bonfire-networks/bonfire-app.git "${APP_DIR}"
|
||||
else
|
||||
info "Repository already present; updating..."
|
||||
sudo -u "${APP_USER}" bash -lc "cd '${APP_DIR}' && git fetch --depth 1 origin && git reset --hard origin/HEAD"
|
||||
fi
|
||||
|
||||
### ---------------------------
|
||||
### Install ASDF plugins & runtimes as bonfire user
|
||||
### ---------------------------
|
||||
log "Installing ASDF plugins (erlang, elixir, nodejs) and runtimes (may be slow)"
|
||||
sudo -u "${APP_USER}" bash -lc "
|
||||
set -euo pipefail
|
||||
export HOME='${APP_HOME}'
|
||||
. \"\$HOME/.asdf/asdf.sh\"
|
||||
|
||||
asdf plugin add erlang || true
|
||||
asdf plugin add elixir || true
|
||||
asdf plugin add nodejs || true
|
||||
|
||||
# nodejs key import helper (if present)
|
||||
if [[ -f \"\$HOME/.asdf/plugins/nodejs/bin/import-release-team-keyring\" ]]; then
|
||||
bash \"\$HOME/.asdf/plugins/nodejs/bin/import-release-team-keyring\" || true
|
||||
fi
|
||||
|
||||
cd '${APP_DIR}'
|
||||
if [[ -f .tool-versions ]]; then
|
||||
export KERL_CONFIGURE_OPTIONS=\"${KERL_CONFIGURE_OPTIONS}\"
|
||||
asdf install || true
|
||||
else
|
||||
export KERL_CONFIGURE_OPTIONS=\"${KERL_CONFIGURE_OPTIONS}\"
|
||||
EL_VER=\$(asdf list-all elixir | tail -n1 || true)
|
||||
ERL_VER=\$(asdf list-all erlang | tail -n1 || true)
|
||||
NODE_VER=\$(asdf list-all nodejs | tail -n1 || true)
|
||||
if [[ -n \"\$ERL_VER\" ]]; then asdf install erlang \"\$ERL_VER\" || true; asdf global erlang \"\$ERL_VER\"; fi
|
||||
if [[ -n \"\$EL_VER\" ]]; then asdf install elixir \"\$EL_VER\" || true; asdf global elixir \"\$EL_VER\"; fi
|
||||
if [[ -n \"\$NODE_VER\" ]]; then asdf install nodejs \"\$NODE_VER\" || true; asdf global nodejs \"\$NODE_VER\"; fi
|
||||
fi
|
||||
|
||||
mix local.hex --force || true
|
||||
mix local.rebar --force || true
|
||||
|
||||
echo '[info] ASDF runtimes installed (or attempted).'
|
||||
"
|
||||
|
||||
### ---------------------------
|
||||
### Verify mix is available
|
||||
### ---------------------------
|
||||
if ! sudo -u "${APP_USER}" bash -lc ". ${APP_HOME}/.asdf/asdf.sh && command -v mix >/dev/null 2>&1"; then
|
||||
die "mix not available for ${APP_USER}. Check ASDF install logs above."
|
||||
fi
|
||||
|
||||
### ---------------------------
|
||||
### DNS validation (unless skipped)
|
||||
### ---------------------------
|
||||
if [[ "${SKIP_DNS_CHECK}" -eq 0 ]]; then
|
||||
log "Checking DNS A/AAAA records for ${DOMAIN}"
|
||||
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
|
||||
echo " Server IPv4: ${SERVER_V4:-<none>} Domain A: ${DOMAIN_A:-<none>}"
|
||||
echo " Server IPv6: ${SERVER_V6:-<none>} Domain AAAA: ${DOMAIN_AAAA:-<none>}"
|
||||
DNS_OK=false
|
||||
if [[ -n "${SERVER_V4}" && "${SERVER_V4}" == "${DOMAIN_A}" ]]; then DNS_OK=true; fi
|
||||
if [[ -n "${SERVER_V6}" && -n "${DOMAIN_AAAA}" && "${SERVER_V6,,}" == "${DOMAIN_AAAA,,}" ]]; then DNS_OK=true; fi
|
||||
if [[ "${DNS_OK}" != "true" ]]; then
|
||||
die "DNS for ${DOMAIN} does not point to this server's public IP. Fix DNS or re-run with --skip-dns-check."
|
||||
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}';
|
||||
### ---------------------------
|
||||
### PostgreSQL role / DB / extensions
|
||||
### ---------------------------
|
||||
log "Configuring PostgreSQL role/database and enabling PostGIS & citext"
|
||||
sudo -u postgres psql -v ON_ERROR_STOP=1 <<PSQL || die "postgres setup failed"
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${PG_USER}') THEN
|
||||
CREATE ROLE ${PG_USER} LOGIN PASSWORD '${PG_PASS}';
|
||||
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;"
|
||||
END
|
||||
\$\$;
|
||||
PSQL
|
||||
|
||||
# ---------- 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
|
||||
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${PG_DB}'" | grep -q 1; then
|
||||
sudo -u postgres createdb -O "${PG_USER}" "${PG_DB}"
|
||||
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}"
|
||||
sudo -u postgres psql -d "${PG_DB}" -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS postgis;" || die "Failed enabling postgis"
|
||||
sudo -u postgres psql -d "${PG_DB}" -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS citext;" || warn "Could not enable citext (may require superuser privileges)."
|
||||
|
||||
### ---------------------------
|
||||
### Build Bonfire (as bonfire user)
|
||||
### ---------------------------
|
||||
log "Writing .env and building Bonfire (this can take many minutes)"
|
||||
# ensure webroot exists for ACME challenge
|
||||
mkdir -p /var/www/html
|
||||
chown -R "${APP_USER}:${APP_USER}" /var/www/html
|
||||
|
||||
sudo -u "${APP_USER}" bash -lc "
|
||||
set -euo pipefail
|
||||
export HOME='${APP_HOME}'
|
||||
. \"\$HOME/.asdf/asdf.sh\" || true
|
||||
export FLAVOUR='${FLAVOUR}'
|
||||
export MIX_ENV=prod
|
||||
export WITH_DOCKER=no
|
||||
cd '${APP_DIR}'
|
||||
|
||||
echo "=== Initializing config with 'just config' ==="
|
||||
just config
|
||||
if command -v just >/dev/null 2>&1; then
|
||||
just config || true
|
||||
fi
|
||||
|
||||
SECRET_KEY_BASE=\$(openssl rand -hex 64)
|
||||
SIGNING_SALT=\$(openssl rand -hex 16)
|
||||
ENCRYPTION_SALT=\$(openssl rand -hex 16)
|
||||
MEILI_MASTER_KEY=\$(openssl rand -hex 32)
|
||||
|
||||
cat > .env <<EOF_ENV
|
||||
FLAVOUR=${FLAVOUR}
|
||||
HOSTNAME=${DOMAIN}
|
||||
|
||||
DATABASE_URL=ecto://${PG_USER}:${PG_PASS}@localhost/${PG_DB}
|
||||
|
||||
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)
|
||||
SIGNING_SALT=${SIGNING_SALT}
|
||||
ENCRYPTION_SALT=${ENCRYPTION_SALT}
|
||||
MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
|
||||
WITH_PATHEX=1
|
||||
# PORT=4000
|
||||
EOF
|
||||
EOF_ENV
|
||||
|
||||
# ---------- build release ----------
|
||||
echo "=== Running 'just setup-prod' and 'just rel-build' ==="
|
||||
just setup-prod
|
||||
just rel-build
|
||||
# Disable Pathex if Elixir bug-window
|
||||
if command -v elixir >/dev/null 2>&1; then
|
||||
ELV=\$(elixir -v | sed -n 's/.*Elixir \\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\).*/\\1/p' || true)
|
||||
if printf '%s\n' 1.17.0 1.17.1 1.17.2 | grep -qx \"\$ELV\"; then
|
||||
sed -i 's/^WITH_PATHEX=.*/WITH_PATHEX=0/' .env || true
|
||||
echo '[warn] Elixir in bug-window; disabled WITH_PATHEX in .env'
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------- Nginx + Let's Encrypt ----------
|
||||
echo "=== Installing Nginx + Certbot ==="
|
||||
sudo apt install -y nginx certbot python3-certbot-nginx
|
||||
mix local.hex --force || true
|
||||
mix local.rebar --force || true
|
||||
|
||||
mix deps.get --only prod || mix deps.get || true
|
||||
# Assets
|
||||
if [[ -d assets ]]; then
|
||||
cd assets
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
yarn install --frozen-lockfile || yarn install || true
|
||||
NODE_ENV=production yarn build || true
|
||||
else
|
||||
npm ci || npm install || true
|
||||
NODE_ENV=production npm run build || true
|
||||
fi
|
||||
cd -
|
||||
fi
|
||||
|
||||
if command -v just >/dev/null 2>&1; then
|
||||
just setup-prod || true
|
||||
just rel-build || true
|
||||
else
|
||||
MIX_ENV=prod mix compile
|
||||
MIX_ENV=prod mix assets.deploy || true
|
||||
MIX_ENV=prod mix ecto.setup || true
|
||||
MIX_ENV=prod mix release
|
||||
fi
|
||||
|
||||
echo '[info] Bonfire build attempted (see output above).'
|
||||
"
|
||||
|
||||
# verify release binary
|
||||
if [[ ! -x "${APP_DIR}/_build/prod/rel/bonfire/bin/bonfire" ]]; then
|
||||
die "Release binary missing at ${APP_DIR}/_build/prod/rel/bonfire/bin/bonfire — check the build output above."
|
||||
fi
|
||||
|
||||
### ---------------------------
|
||||
### Nginx config + certbot
|
||||
### ---------------------------
|
||||
log "Writing nginx site config and reloading"
|
||||
SITE_AVAIL="/etc/nginx/sites-available/bonfire"
|
||||
SITE_LINK="/etc/nginx/sites-enabled/bonfire"
|
||||
APP_UPSTREAM="http://127.0.0.1:4000"
|
||||
APP_UPSTREAM="http://127.0.0.1:${APP_PORT}"
|
||||
|
||||
echo "=== Writing Nginx site config ==="
|
||||
sudo tee "${SITE_AVAIL}" >/dev/null <<NGINX
|
||||
cat > "${SITE_AVAIL}" <<NGINXCONF
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${DOMAIN};
|
||||
|
||||
# Proxy all requests to Phoenix (serves static as well)
|
||||
# ACME challenge path
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
default_type text/plain;
|
||||
}
|
||||
|
||||
location /live/websocket {
|
||||
proxy_pass ${APP_UPSTREAM};
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection \"upgrade\";
|
||||
proxy_set_header Host \$host;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass ${APP_UPSTREAM};
|
||||
proxy_http_version 1.1;
|
||||
|
|
@ -209,108 +423,115 @@ server {
|
|||
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";
|
||||
proxy_set_header Connection \"upgrade\";
|
||||
}
|
||||
}
|
||||
NGINX
|
||||
NGINXCONF
|
||||
|
||||
sudo ln -sf "${SITE_AVAIL}" "${SITE_LINK}"
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
ln -sf "${SITE_AVAIL}" "${SITE_LINK}"
|
||||
nginx -t || die "nginx config test failed"
|
||||
systemctl restart nginx
|
||||
|
||||
echo "=== Obtaining Let's Encrypt certificate ==="
|
||||
sudo certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos -m "${EMAIL}"
|
||||
log "Attempting to obtain Let's Encrypt certificate via certbot"
|
||||
if ! certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos -m "${EMAIL}"; then
|
||||
warn "Certbot failed to obtain certificate. Check DNS/ports and run: certbot --nginx -d ${DOMAIN} manually."
|
||||
fi
|
||||
systemctl enable --now certbot.timer || true
|
||||
|
||||
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
|
||||
### ---------------------------
|
||||
### systemd unit
|
||||
### ---------------------------
|
||||
log "Writing systemd service unit for bonfire"
|
||||
SERVICE_PATH="/etc/systemd/system/bonfire.service"
|
||||
cat > "${SERVICE_PATH}" <<SYSTEMD
|
||||
[Unit]
|
||||
Description=Bonfire App
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
After=network-online.target postgresql.service
|
||||
Wants=network-online.target postgresql.service
|
||||
|
||||
[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
|
||||
User=${APP_USER}
|
||||
Group=${APP_USER}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
EnvironmentFile=${APP_DIR}/.env
|
||||
Environment=LANG=en_US.UTF-8
|
||||
Environment=LC_ALL=en_US.UTF-8
|
||||
Environment=ELIXIR_ERL_OPTIONS=+fnu
|
||||
ExecStart=${APP_DIR}/_build/prod/rel/bonfire/bin/bonfire daemon
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SYSTEMD
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now bonfire
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now bonfire || warn "systemd enable/start returned non-zero; check journalctl -u bonfire"
|
||||
|
||||
# ---------- helper scripts ----------
|
||||
echo "=== Creating helper scripts ==="
|
||||
cat > "${HOME}/bonfire/reload-nginx.sh" <<'EOS'
|
||||
### ---------------------------
|
||||
### Helper scripts (bonfire user)
|
||||
### ---------------------------
|
||||
log "Installing helper scripts into ${APP_DIR}"
|
||||
sudo -u "${APP_USER}" bash -lc "cat > '${APP_DIR}/reload-nginx.sh' <<'EOS'
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
echo "Nginx reloaded."
|
||||
echo 'Nginx reloaded.'
|
||||
EOS
|
||||
chmod +x "${HOME}/bonfire/reload-nginx.sh"
|
||||
chmod +x '${APP_DIR}/reload-nginx.sh' || true"
|
||||
|
||||
cat > "${HOME}/bonfire/bonfire-deploy.sh" <<'EOS'
|
||||
sudo -u "${APP_USER}" bash -lc "cat > '${APP_DIR}/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."
|
||||
cd \"\$(dirname \"\$0\")\"
|
||||
. \"\$HOME/.asdf/asdf.sh\" 2>/dev/null || true
|
||||
echo 'Pulling latest code...'
|
||||
git pull --ff-only || true
|
||||
echo 'Running production setup and rebuild...'
|
||||
just setup-prod || true
|
||||
just rel-build || true
|
||||
echo 'Restarting service...'
|
||||
sudo systemctl restart bonfire || true
|
||||
echo 'Reloading nginx...'
|
||||
./reload-nginx.sh || true
|
||||
echo 'Deploy complete.'
|
||||
EOS
|
||||
chmod +x "${HOME}/bonfire/bonfire-deploy.sh"
|
||||
chmod +x '${APP_DIR}/bonfire-deploy.sh' || true"
|
||||
|
||||
# ---------- 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"
|
||||
### ---------------------------
|
||||
### Final summary
|
||||
### ---------------------------
|
||||
log "Done (or attempted). Summary:"
|
||||
cat <<EOF
|
||||
|
||||
Bonfire URL: https://${DOMAIN}
|
||||
|
||||
Systemd:
|
||||
sudo systemctl status bonfire
|
||||
sudo journalctl -u bonfire -f
|
||||
|
||||
Database:
|
||||
DB_NAME: ${PG_DB}
|
||||
DB_USER: ${PG_USER}
|
||||
DB_PASSWORD: ${PG_PASS}
|
||||
|
||||
To run CLI as bonfire user:
|
||||
sudo -u ${APP_USER} -H bash -c "cd ${APP_DIR} && bash"
|
||||
|
||||
Useful commands:
|
||||
sudo -u ${APP_USER} -H bash -c 'cd ${APP_DIR} && just rel-build'
|
||||
sudo systemctl restart bonfire
|
||||
sudo systemctl status bonfire
|
||||
journalctl -u bonfire -b --no-pager
|
||||
|
||||
If something failed: inspect the build output above and the journal:
|
||||
journalctl -u bonfire -b --no-pager
|
||||
tail -n 200 /var/log/syslog
|
||||
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue