Changing approach
This commit is contained in:
parent
7325976ebd
commit
4fa50fd49e
|
|
@ -1,207 +1,421 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
set -euo pipefail
|
||||||
|
|
||||||
# ============================
|
### ---------------------------
|
||||||
# Bonfire Bare-Metal Installer
|
### Configuration defaults
|
||||||
# Ubuntu 24.04 LTS (Noble)
|
### ---------------------------
|
||||||
# ============================
|
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 ----------
|
require_root(){
|
||||||
read -rp "Enter your domain name (FQDN, e.g., example.com): " DOMAIN
|
if [[ $EUID -ne 0 ]]; then
|
||||||
while [[ -z "${DOMAIN}" ]]; do
|
die "This script must be run as root (sudo)."
|
||||||
echo "Domain cannot be empty."
|
fi
|
||||||
read -rp "Enter your domain name: " DOMAIN
|
}
|
||||||
|
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
|
done
|
||||||
|
|
||||||
read -rp "Contact email for Let's Encrypt (e.g., admin@${DOMAIN}): " EMAIL
|
### ---------------------------
|
||||||
while [[ -z "${EMAIL}" ]]; do
|
### Interactive prompts
|
||||||
echo "Email cannot be empty."
|
### ---------------------------
|
||||||
read -rp "Contact email for Let's Encrypt: " EMAIL
|
read -rp "Enter your domain (FQDN, e.g. bonfire.example.com): " DOMAIN
|
||||||
done
|
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
|
read -rp "Bonfire flavour [social|ember|community|cooperation] (default: social): " FLAVOUR
|
||||||
FLAVOUR=${FLAVOUR:-social}
|
FLAVOUR=${FLAVOUR:-social}
|
||||||
|
|
||||||
read -rp "PostgreSQL username (default: bonfire_user): " DB_USER
|
read -rp "PostgreSQL role to create (default: ${PG_USER_DEFAULT}): " PG_USER
|
||||||
DB_USER=${DB_USER:-bonfire_user}
|
PG_USER=${PG_USER:-$PG_USER_DEFAULT}
|
||||||
DB_NAME="bonfire"
|
PG_PASS="$(openssl rand -base64 24)"
|
||||||
|
|
||||||
# we'll generate a random DB password
|
log "Inputs summary:"
|
||||||
DB_PASSWORD="$(openssl rand -base64 24)"
|
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,}$'
|
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
|
if [[ ! "$DOMAIN" =~ $FQDN_REGEX ]]; then
|
||||||
echo "❌ Invalid FQDN: ${DOMAIN}"
|
die "Invalid FQDN: $DOMAIN"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- DNS checks (A/AAAA must match this server) ----------
|
### ---------------------------
|
||||||
echo "=== Checking DNS for ${DOMAIN} ==="
|
### System update & ensure curl/gnupg early
|
||||||
# public IPv4
|
### ---------------------------
|
||||||
SERVER_V4="$(curl -4 -fsS https://api.ipify.org || true)"
|
log "APT update & ensure curl/gnupg are present (required early for repos)"
|
||||||
# public IPv6 (may be empty if host lacks IPv6)
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
SERVER_V6="$(curl -6 -fsS https://api64.ipify.org || true)"
|
apt-get update -y
|
||||||
|
apt-get upgrade -y
|
||||||
|
|
||||||
# need dig
|
# Ensure curl / gnupg are present early for repo keys
|
||||||
if ! command -v dig >/dev/null 2>&1; then
|
apt-get install -y --no-install-recommends curl wget gnupg gpg-agent dirmngr lsb-release apt-transport-https ca-certificates || true
|
||||||
echo "Installing dnsutils for DNS checks..."
|
|
||||||
sudo apt update && sudo apt install -y dnsutils
|
### ---------------------------
|
||||||
|
### 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
|
fi
|
||||||
|
|
||||||
DOMAIN_A="$(dig +short A "${DOMAIN}" | head -n1 || true)"
|
### ---------------------------
|
||||||
DOMAIN_AAAA="$(dig +short AAAA "${DOMAIN}" | head -n1 || true)"
|
### 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"
|
||||||
|
|
||||||
echo "Server IPv4: ${SERVER_V4:-<none>}"
|
# ensure curl & gpg are present
|
||||||
echo "Server IPv6: ${SERVER_V6:-<none>}"
|
apt-get update -y
|
||||||
echo "Domain A: ${DOMAIN_A:-<none>}"
|
apt-get install -y --no-install-recommends curl gnupg lsb-release apt-transport-https ca-certificates || true
|
||||||
echo "Domain AAAA: ${DOMAIN_AAAA:-<none>}"
|
|
||||||
|
|
||||||
MISMATCH=true
|
# import/dearmor the key if missing
|
||||||
if [[ -n "${SERVER_V4}" && -n "${DOMAIN_A}" && "${SERVER_V4}" == "${DOMAIN_A}" ]]; then
|
if [[ ! -f "${TRUSTED_KEY}" ]]; then
|
||||||
MISMATCH=false
|
info "Importing PostgreSQL signing key to ${TRUSTED_KEY}..."
|
||||||
fi
|
curl -fsSL "${PG_KEY_URL}" | gpg --dearmor -o "${TRUSTED_KEY}" \
|
||||||
if [[ -n "${SERVER_V6}" && -n "${DOMAIN_AAAA}" && "${SERVER_V6,,}" == "${DOMAIN_AAAA,,}" ]]; then
|
|| { rm -f "${TRUSTED_KEY}"; die "Failed to fetch/dearmor PGDG key"; }
|
||||||
MISMATCH=false
|
chmod 644 "${TRUSTED_KEY}"
|
||||||
|
chown root:root "${TRUSTED_KEY}"
|
||||||
|
else
|
||||||
|
info "PGDG key already present at ${TRUSTED_KEY}; skipping import."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${MISMATCH}" == "true" ]]; then
|
# write the repo list (idempotent)
|
||||||
echo "❌ DNS for ${DOMAIN} does not resolve to this server."
|
echo "deb http://apt.postgresql.org/pub/repos/apt ${CODENAME}-pgdg main" > "${LIST_FILE}"
|
||||||
echo " Server IPv4: ${SERVER_V4:-<none>}, Domain A: ${DOMAIN_A:-<none>}"
|
chmod 644 "${LIST_FILE}"
|
||||||
echo " Server IPv6: ${SERVER_V6:-<none>}, Domain AAAA: ${DOMAIN_AAAA:-<none>}"
|
info "PGDG APT entry written to ${LIST_FILE}"
|
||||||
echo "➡️ Fix your DNS A/AAAA records, wait for propagation, then re-run."
|
|
||||||
exit 1
|
# 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
|
fi
|
||||||
|
|
||||||
echo "✅ DNS looks good."
|
# 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
|
||||||
|
|
||||||
# ---------- system prep ----------
|
# create local bin dir (nodejs plugin helper uses it)
|
||||||
echo "=== Updating system packages ==="
|
sudo -u "${APP_USER}" bash -lc "mkdir -p ${APP_HOME}/.local/bin && chmod 755 ${APP_HOME}/.local/bin"
|
||||||
sudo apt update && sudo apt -y upgrade
|
|
||||||
|
|
||||||
echo "=== Installing base tools ==="
|
### ---------------------------
|
||||||
sudo apt install -y \
|
### Clone Bonfire repository
|
||||||
curl git build-essential ca-certificates jq \
|
### ---------------------------
|
||||||
libssl-dev libffi-dev libncurses5-dev \
|
log "Cloning Bonfire repository into ${APP_DIR}"
|
||||||
libwxgtk3.0-gtk3-dev libgl1-mesa-dev libglu1-mesa-dev \
|
if [[ ! -d "${APP_DIR}" ]]; then
|
||||||
libpng-dev libssh-dev unixodbc-dev pkg-config
|
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
|
||||||
|
|
||||||
echo "=== Installing 'just' command runner ==="
|
### ---------------------------
|
||||||
sudo apt install -y just
|
### 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\"
|
||||||
|
|
||||||
echo "=== Installing Node.js 20 + Yarn ==="
|
asdf plugin add erlang || true
|
||||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
asdf plugin add elixir || true
|
||||||
sudo apt install -y nodejs
|
asdf plugin add nodejs || true
|
||||||
npm install -g yarn
|
|
||||||
|
|
||||||
# ---------- PostgreSQL 17 + PostGIS ----------
|
# nodejs key import helper (if present)
|
||||||
echo "=== Installing PostgreSQL 17 + PostGIS ==="
|
if [[ -f \"\$HOME/.asdf/plugins/nodejs/bin/import-release-team-keyring\" ]]; then
|
||||||
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
|
bash \"\$HOME/.asdf/plugins/nodejs/bin/import-release-team-keyring\" || true
|
||||||
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/pgdg.gpg
|
fi
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y postgresql-17 postgresql-17-postgis-3
|
|
||||||
|
|
||||||
echo "=== Creating PostgreSQL role & database ==="
|
cd '${APP_DIR}'
|
||||||
sudo -u postgres psql -tAc "DO \$\$ BEGIN
|
if [[ -f .tool-versions ]]; then
|
||||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USER}') THEN
|
export KERL_CONFIGURE_OPTIONS=\"${KERL_CONFIGURE_OPTIONS}\"
|
||||||
CREATE ROLE ${DB_USER} LOGIN PASSWORD '${DB_PASSWORD}';
|
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)"
|
||||||
|
SERVER_V6="$(curl -6 -fsS https://api64.ipify.org || true)"
|
||||||
|
DOMAIN_A="$(dig +short A "${DOMAIN}" | head -n1 || true)"
|
||||||
|
DOMAIN_AAAA="$(dig +short AAAA "${DOMAIN}" | head -n1 || true)"
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
### ---------------------------
|
||||||
|
### 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 IF;
|
||||||
END \$\$;"
|
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;"
|
PSQL
|
||||||
|
|
||||||
# ---------- asdf + mise (match .tool-versions) ----------
|
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${PG_DB}'" | grep -q 1; then
|
||||||
echo "=== Installing asdf (plugins: erlang, elixir) ==="
|
sudo -u postgres createdb -O "${PG_USER}" "${PG_DB}"
|
||||||
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
|
fi
|
||||||
|
|
||||||
# ---------- environment ----------
|
sudo -u postgres psql -d "${PG_DB}" -v ON_ERROR_STOP=1 -c "CREATE EXTENSION IF NOT EXISTS postgis;" || die "Failed enabling postgis"
|
||||||
echo "=== Exporting environment ==="
|
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)."
|
||||||
grep -q 'FLAVOUR=' "$HOME/.bashrc" || {
|
|
||||||
echo "export FLAVOUR=${FLAVOUR}" >> "$HOME/.bashrc"
|
### ---------------------------
|
||||||
echo "export MIX_ENV=prod" >> "$HOME/.bashrc"
|
### Build Bonfire (as bonfire user)
|
||||||
echo "export WITH_DOCKER=no" >> "$HOME/.bashrc"
|
### ---------------------------
|
||||||
}
|
log "Writing .env and building Bonfire (this can take many minutes)"
|
||||||
export FLAVOUR="${FLAVOUR}"
|
# 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 MIX_ENV=prod
|
||||||
export WITH_DOCKER=no
|
export WITH_DOCKER=no
|
||||||
|
cd '${APP_DIR}'
|
||||||
|
|
||||||
echo "=== Initializing config with 'just config' ==="
|
if command -v just >/dev/null 2>&1; then
|
||||||
just config
|
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}
|
SECRET_KEY_BASE=${SECRET_KEY_BASE}
|
||||||
WITH_PATHEX=${WITH_PATHEX}
|
SIGNING_SALT=${SIGNING_SALT}
|
||||||
# PORT can be set (defaults to 4000)
|
ENCRYPTION_SALT=${ENCRYPTION_SALT}
|
||||||
|
MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||||
|
|
||||||
|
WITH_PATHEX=1
|
||||||
# PORT=4000
|
# PORT=4000
|
||||||
EOF
|
EOF_ENV
|
||||||
|
|
||||||
# ---------- build release ----------
|
# Disable Pathex if Elixir bug-window
|
||||||
echo "=== Running 'just setup-prod' and 'just rel-build' ==="
|
if command -v elixir >/dev/null 2>&1; then
|
||||||
just setup-prod
|
ELV=\$(elixir -v | sed -n 's/.*Elixir \\([0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\).*/\\1/p' || true)
|
||||||
just rel-build
|
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 ----------
|
mix local.hex --force || true
|
||||||
echo "=== Installing Nginx + Certbot ==="
|
mix local.rebar --force || true
|
||||||
sudo apt install -y nginx certbot python3-certbot-nginx
|
|
||||||
|
|
||||||
|
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_AVAIL="/etc/nginx/sites-available/bonfire"
|
||||||
SITE_LINK="/etc/nginx/sites-enabled/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 ==="
|
cat > "${SITE_AVAIL}" <<NGINXCONF
|
||||||
sudo tee "${SITE_AVAIL}" >/dev/null <<NGINX
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name ${DOMAIN};
|
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 / {
|
location / {
|
||||||
proxy_pass ${APP_UPSTREAM};
|
proxy_pass ${APP_UPSTREAM};
|
||||||
proxy_http_version 1.1;
|
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-For \$proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
proxy_set_header Upgrade \$http_upgrade;
|
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}"
|
ln -sf "${SITE_AVAIL}" "${SITE_LINK}"
|
||||||
sudo nginx -t
|
nginx -t || die "nginx config test failed"
|
||||||
sudo systemctl restart nginx
|
systemctl restart nginx
|
||||||
|
|
||||||
echo "=== Obtaining Let's Encrypt certificate ==="
|
log "Attempting to obtain Let's Encrypt certificate via certbot"
|
||||||
sudo certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos -m "${EMAIL}"
|
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 unit
|
||||||
|
### ---------------------------
|
||||||
# ---------- systemd service ----------
|
log "Writing systemd service unit for bonfire"
|
||||||
echo "=== Creating systemd service 'bonfire.service' ==="
|
SERVICE_PATH="/etc/systemd/system/bonfire.service"
|
||||||
SERVICE_FILE="/etc/systemd/system/bonfire.service"
|
cat > "${SERVICE_PATH}" <<SYSTEMD
|
||||||
RUN_USER="$(id -un)"
|
|
||||||
RUN_GROUP="$(id -gn)"
|
|
||||||
|
|
||||||
sudo tee "${SERVICE_FILE}" >/dev/null <<SYSTEMD
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Bonfire App
|
Description=Bonfire App
|
||||||
After=network-online.target
|
After=network-online.target postgresql.service
|
||||||
Wants=network-online.target
|
Wants=network-online.target postgresql.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=${RUN_USER}
|
User=${APP_USER}
|
||||||
Group=${RUN_GROUP}
|
Group=${APP_USER}
|
||||||
WorkingDirectory=${HOME}/bonfire
|
WorkingDirectory=${APP_DIR}
|
||||||
EnvironmentFile=${HOME}/bonfire/.env
|
EnvironmentFile=${APP_DIR}/.env
|
||||||
ExecStart=${HOME}/bonfire/_build/prod/rel/bonfire/bin/bonfire start daemon
|
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
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
LimitNOFILE=65536
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
SYSTEMD
|
SYSTEMD
|
||||||
|
|
||||||
sudo systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
sudo systemctl enable --now bonfire
|
systemctl enable --now bonfire || warn "systemd enable/start returned non-zero; check journalctl -u bonfire"
|
||||||
|
|
||||||
# ---------- helper scripts ----------
|
### ---------------------------
|
||||||
echo "=== Creating helper scripts ==="
|
### Helper scripts (bonfire user)
|
||||||
cat > "${HOME}/bonfire/reload-nginx.sh" <<'EOS'
|
### ---------------------------
|
||||||
|
log "Installing helper scripts into ${APP_DIR}"
|
||||||
|
sudo -u "${APP_USER}" bash -lc "cat > '${APP_DIR}/reload-nginx.sh' <<'EOS'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
sudo nginx -t
|
sudo nginx -t
|
||||||
sudo systemctl reload nginx
|
sudo systemctl reload nginx
|
||||||
echo "Nginx reloaded."
|
echo 'Nginx reloaded.'
|
||||||
EOS
|
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
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
cd \"\$(dirname \"\$0\")\"
|
||||||
|
. \"\$HOME/.asdf/asdf.sh\" 2>/dev/null || true
|
||||||
echo "Pulling latest code..."
|
echo 'Pulling latest code...'
|
||||||
git pull --ff-only
|
git pull --ff-only || true
|
||||||
|
echo 'Running production setup and rebuild...'
|
||||||
echo "Ensuring tool versions are installed (mise)..."
|
just setup-prod || true
|
||||||
~/.local/bin/mise install
|
just rel-build || true
|
||||||
|
echo 'Restarting service...'
|
||||||
echo "Updating prod config..."
|
sudo systemctl restart bonfire || true
|
||||||
just setup-prod
|
echo 'Reloading nginx...'
|
||||||
|
./reload-nginx.sh || true
|
||||||
echo "Building release..."
|
echo 'Deploy complete.'
|
||||||
just rel-build
|
|
||||||
|
|
||||||
echo "Restarting Bonfire..."
|
|
||||||
sudo systemctl restart bonfire
|
|
||||||
|
|
||||||
echo "Reloading Nginx..."
|
|
||||||
./reload-nginx.sh
|
|
||||||
|
|
||||||
echo "Deploy complete."
|
|
||||||
EOS
|
EOS
|
||||||
chmod +x "${HOME}/bonfire/bonfire-deploy.sh"
|
chmod +x '${APP_DIR}/bonfire-deploy.sh' || true"
|
||||||
|
|
||||||
# ---------- done ----------
|
### ---------------------------
|
||||||
echo
|
### Final summary
|
||||||
echo "=== Bonfire setup complete! 🎉 ==="
|
### ---------------------------
|
||||||
echo "Site: https://${DOMAIN}"
|
log "Done (or attempted). Summary:"
|
||||||
echo
|
cat <<EOF
|
||||||
echo "Systemd service controls:"
|
|
||||||
echo " sudo systemctl status bonfire"
|
Bonfire URL: https://${DOMAIN}
|
||||||
echo " sudo systemctl restart bonfire"
|
|
||||||
echo " sudo systemctl stop bonfire"
|
Systemd:
|
||||||
echo
|
sudo systemctl status bonfire
|
||||||
echo "Database credentials:"
|
sudo journalctl -u bonfire -f
|
||||||
echo " DB_USER: ${DB_USER}"
|
|
||||||
echo " DB_PASSWORD: ${DB_PASSWORD}"
|
Database:
|
||||||
echo " DB_NAME: ${DB_NAME}"
|
DB_NAME: ${PG_DB}
|
||||||
echo
|
DB_USER: ${PG_USER}
|
||||||
echo "Deploy updates with:"
|
DB_PASSWORD: ${PG_PASS}
|
||||||
echo " ${HOME}/bonfire/bonfire-deploy.sh"
|
|
||||||
echo "Reload Nginx with:"
|
To run CLI as bonfire user:
|
||||||
echo " ${HOME}/bonfire/reload-nginx.sh"
|
sudo -u ${APP_USER} -H bash -c "cd ${APP_DIR} && bash"
|
||||||
echo
|
|
||||||
echo "Certbot auto-renewal is enabled. To force renew:"
|
Useful commands:
|
||||||
echo " sudo certbot renew && sudo systemctl reload nginx"
|
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