#!/usr/bin/env bash # Installs Cross Events on a fresh Ubuntu server (22.04 or 24.04): Node 20, PostgreSQL, PM2, # nginx and an HTTPS certificate, then the app itself. Finish in the browser at /setup. # # Usage (as root, or with sudo): # sudo bash install.sh --domain events.yourchurch.org --email you@yourchurch.org # # Options: # --domain the site's address (required); DNS must already point at this server # --email for the HTTPS certificate (Let's Encrypt) notices (required unless --no-ssl) # --repo git URL (default: https://c-git.crosscode.co.za/joshua/hope-events.git) # --version tag to install, e.g. v1.41.0 (default: the latest main) # --dir where to install (default: /opt/hope-events) # --user Linux user that owns and runs the app (default: hopeevents, created if needed) # --name PM2 / nginx name, unique per install on a server (default: hope-events) # --web-port frontend port (default: 3000) --api-port backend port (default: 5000) # --no-ssl skip the certificate (e.g. behind another proxy that does HTTPS) # # Re-running is safe: existing database credentials, .env files and PM2 config are kept. # Updates after that: `bash scripts/deploy.sh vX.Y.Z` from the install folder. set -Eeuo pipefail DOMAIN=""; EMAIL=""; REPO="https://c-git.crosscode.co.za/joshua/hope-events.git"; VERSION="main" DIR="/opt/hope-events"; APP_USER="hopeevents"; NAME="hope-events"; WEB_PORT=3000; API_PORT=5000; SSL=1 while [ $# -gt 0 ]; do case "$1" in --domain) DOMAIN="$2"; shift 2 ;; --email) EMAIL="$2"; shift 2 ;; --repo) REPO="$2"; shift 2 ;; --version) VERSION="$2"; shift 2 ;; --dir) DIR="$2"; shift 2 ;; --user) APP_USER="$2"; shift 2 ;; --name) NAME="$2"; shift 2 ;; --web-port) WEB_PORT="$2"; shift 2 ;; --api-port) API_PORT="$2"; shift 2 ;; --no-ssl) SSL=0; shift ;; -h|--help) sed -n '2,22p' "$0"; exit 0 ;; *) echo "Unknown option: $1 (see --help)"; exit 1 ;; esac done fail() { echo "ERROR: $*" >&2; exit 1; } step() { echo; echo "==> $*"; } [ "$(id -u)" = 0 ] || fail "Run as root (sudo bash install.sh …)." [ -n "$DOMAIN" ] || fail "--domain is required." [ "$SSL" = 0 ] || [ -n "$EMAIL" ] || fail "--email is required for the HTTPS certificate (or use --no-ssl)." [[ "$NAME" =~ ^[a-z0-9-]+$ ]] || fail "--name may only use a-z, 0-9 and -." DB_NAME="$(echo "$NAME" | tr '-' '_')" SCHEME="https"; [ "$SSL" = 1 ] || SCHEME="http" SITE_URL="$SCHEME://$DOMAIN" as_app() { sudo -u "$APP_USER" -H bash -lc "$*"; } random() { openssl rand -base64 48 | tr -d '/+=\n' | head -c "${1:-48}"; } step "System packages" export DEBIAN_FRONTEND=noninteractive apt-get update -q apt-get install -y -q curl git nginx postgresql postgresql-client openssl build-essential ca-certificates gnupg [ "$SSL" = 0 ] || apt-get install -y -q certbot python3-certbot-nginx step "Node.js 20" if ! command -v node >/dev/null || [ "$(node -p 'process.versions.node.split(".")[0]')" -lt 20 ]; then curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y -q nodejs fi node -v command -v pm2 >/dev/null || npm install -g pm2 step "App user ($APP_USER)" id "$APP_USER" >/dev/null 2>&1 || useradd --create-home --shell /bin/bash "$APP_USER" step "Database ($DB_NAME)" CRED_FILE="/root/.${NAME}-db" if [ -f "$CRED_FILE" ]; then DB_PASS="$(cat "$CRED_FILE")" else DB_PASS="$(random 32)" echo "$DB_PASS" > "$CRED_FILE"; chmod 600 "$CRED_FILE" fi sudo -u postgres psql -v ON_ERROR_STOP=1 -q < "$DIR/backend/.env" < "$DIR/frontend/.env.production" fi if [ ! -f "$DIR/ecosystem.config.js" ]; then sed -e "s/'hope-events-backend'/'$NAME-backend'/" -e "s/'hope-events-frontend'/'$NAME-frontend'/" -e "s/-p 3000/-p $WEB_PORT/" \ "$DIR/ecosystem.config.example.js" > "$DIR/ecosystem.config.js" fi chown "$APP_USER:$APP_USER" "$DIR/backend/.env" "$DIR/frontend/.env.production" "$DIR/ecosystem.config.js" chmod 600 "$DIR/backend/.env" step "Install, migrate, build" as_app "cd '$DIR/backend' && npm ci --no-audit --no-fund && npx prisma migrate deploy && npx prisma generate" as_app "cd '$DIR/frontend' && npm ci --no-audit --no-fund && npm run build" step "Start with PM2" as_app "cd '$DIR' && pm2 startOrReload ecosystem.config.js && pm2 save" env PATH="$PATH:/usr/bin" pm2 startup systemd -u "$APP_USER" --hp "$(eval echo "~$APP_USER")" >/dev/null step "nginx ($DOMAIN)" cat > "/etc/nginx/sites-available/$NAME" </dev/null 2>&1; then break; fi sleep 2 done curl -fsS "http://127.0.0.1:$API_PORT/health" && echo || echo " The backend isn't answering yet — check: sudo -u $APP_USER pm2 logs" echo echo "Done. Open $SITE_URL/setup to create the admin account, then enter the licence key." echo "Database password: $CRED_FILE. Updates: cd $DIR && sudo -u $APP_USER bash scripts/deploy.sh vX.Y.Z"