#!/bin/bash
# b2g India MSME edition: install on a fresh Ubuntu 24.04 server (AWS EC2).
#
# The upload script (upload-to-aws.ps1) copies this folder to ~/b2g-upload on the server together with:
#   parts/b2g-app.zip.001 ...      (the app from the laptop pack)
#   b2g-india-edition-patch.zip    (India front ends, backend changes, sample services, India database)
# Then run:  sudo bash ~/b2g-upload/install-b2g-aws.sh
#
# Safe to re-run: it rebuilds /opt/b2g and reloads the India demo database (test data entered by users is reset).
set -euo pipefail
SRC="$(cd "$(dirname "$0")" && pwd)"
ROOT="${ROOT:-/opt/b2g}"          # install folder
PORT="${PORT:-80}"                 # public web port
IPORT="${IPORT:-9101}"             # first of 16 internal ports (127.0.0.1 only)
SOCK="${SOCK:-/run/php/b2g.sock}"
NGINX_SITE="${NGINX_SITE:-b2g}"
GATE_USER="${GATE_USER:-b2gindia}"   # login gate: one username and password for the whole site
GATE_PASS="${GATE_PASS:-Monsoon-SGW-6543}"
log() { echo; echo "== $*"; }
svc() { if [ -d /run/systemd/system ]; then systemctl "$1" "$2"; else service "$2" "$1"; fi; }   # svc restart nginx

[ "$(id -u)" = 0 ] || { echo "Run with sudo: sudo bash $0"; exit 1; }
ls "$SRC"/parts/b2g-app.zip.* >/dev/null 2>&1 || { echo "Missing $SRC/parts/b2g-app.zip.*"; exit 1; }
[ -f "$SRC/b2g-india-edition-patch.zip" ] || { echo "Missing $SRC/b2g-india-edition-patch.zip"; exit 1; }

RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
if [ "$RAM_MB" -lt 3500 ] && [ ! -f /swapfile ] && [ -z "${NO_SWAP:-}" ]; then
  log "Adding a 2 GB swap file (server has ${RAM_MB} MB RAM)"
  fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile >/dev/null && swapon /swapfile
  grep -q '^/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
WORKERS=$([ "$RAM_MB" -lt 3500 ] && echo 10 || echo 16)

log "Installing nginx, MariaDB and PHP 8.3 (Ubuntu packages)"
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
apt-get install -y -q nginx mariadb-server unzip python3 \
  php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl php8.3-gd php8.3-intl php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath >/dev/null

log "Snapshot of the current version (rollback: sudo b2g-rollback)"
cat > /usr/local/bin/b2g-backup <<'B2GBACKUP'
#!/bin/bash
# b2g India edition: version snapshots on the AWS server, so any install or hotfix can be rolled back.
#   sudo b2g-backup [label]        save a snapshot now (app files, database, web server settings)
#   sudo b2g-rollback              list snapshots
#   sudo b2g-rollback <snapshot>   restore one (a snapshot of the current state is saved first)
# Snapshots live in /opt/b2g/backups/<date-time>-<label>/. The newest 5 are kept (B2G_KEEP to change).
# Not included: BGI's base frontend (app/www, unchanged since the first install) and the Claude API key file.
set -euo pipefail
ROOT="${ROOT:-/opt/b2g}"; BK="$ROOT/backups"; KEEP="${B2G_KEEP:-5}"
DBS="bgi_b2g_master_dev bgi_b2g_reg_dev bgi_b2g_carbon_marketplace bgi_b2g_cengex_dev bgi_b2g_digitall_dev bgi_b2g_gmarket_dev"
CONFS="/etc/nginx/sites-available/b2g /etc/php/8.3/fpm/pool.d/b2g.conf /etc/nginx/b2g-gate.htpasswd"
[ "$(id -u)" = 0 ] || { echo "Run with sudo"; exit 1; }
svc() { if [ -d /run/systemd/system ]; then systemctl "$1" "$2"; else service "$2" "$1"; fi; }

backup() {
  local label="${1:-manual}"; label="${label//[^A-Za-z0-9_-]/}"
  [ -d "$ROOT/app" ] || { echo "Nothing to back up: $ROOT/app not found"; return 0; }
  local free; free=$(df -Pm "$ROOT" | awk 'NR==2{print $4}')
  if [ "${PRUNE:-1}" = 1 ] && [ "${free:-0}" -lt 1500 ]; then echo "Only ${free} MB free on the disk: removing the oldest snapshot first"; ls -1d "$BK"/*/ 2>/dev/null | head -n 1 | xargs -r rm -rf; fi
  local d="$BK/$(date +%Y%m%d-%H%M%S)-$label"; mkdir -p "$d"
  echo "Saving snapshot $d"
  if ! { tar -czf "$d/app.tar.gz" --exclude=app/www -C "$ROOT" app && mysqldump --databases $DBS --routines --triggers --single-transaction | gzip > "$d/db.sql.gz"; }; then
    rm -rf "$d"; echo "Backup FAILED (nothing was changed). Fix the error above before installing anything."; exit 1
  fi
  for f in $CONFS; do [ -f "$f" ] && cp -a --parents "$f" "$d/" ; done
  (cd "$d" && sha256sum app.tar.gz db.sql.gz > SHA256SUMS)
  echo "$(date '+%d %b %Y %H:%M') $label" > "$d/INFO"
  chmod -R go-rwx "$d"
  [ "${PRUNE:-1}" = 1 ] && ls -1d "$BK"/*/ 2>/dev/null | head -n -"$KEEP" | xargs -r rm -rf
  echo "Saved: $(du -sh "$d" | cut -f1). Snapshots kept: $(ls -1d "$BK"/*/ | wc -l)"
}

list() {
  echo "Snapshots in $BK (newest last):"
  for d in "$BK"/*/; do [ -d "$d" ] && printf '  %-40s %s  %s\n' "$(basename "$d")" "$(du -sh "$d" | cut -f1)" "$(cat "$d/INFO" 2>/dev/null)"; done
  echo; echo "Restore one with: sudo b2g-rollback <snapshot name>"
}

restore() {
  local d="$BK/$1"; [ -f "$d/app.tar.gz" ] && [ -f "$d/db.sql.gz" ] || { echo "Snapshot not found: $1"; list; exit 1; }
  (cd "$d" && sha256sum -c --quiet SHA256SUMS) || { echo "Snapshot files are damaged; not restoring"; exit 1; }
  PRUNE=0 backup "before-rollback"   # no pruning here, so the snapshot being restored is never removed
  echo "Restoring $1"
  local keep="$ROOT/app/www"; local tmp; tmp=$(mktemp -d "$ROOT/.restore.XXXX")
  tar -xzf "$d/app.tar.gz" -C "$tmp"
  [ -d "$keep" ] && mv "$keep" "$tmp/app/www"
  rm -rf "$ROOT/app"; mv "$tmp/app" "$ROOT/app"; rm -rf "$tmp"
  gunzip -c "$d/db.sql.gz" | mysql
  [ -d "$d/etc" ] && cp -a "$d/etc/." /etc/
  chown -R www-data:www-data "$ROOT/app"
  nginx -t && svc reload nginx; svc restart php8.3-fpm
  echo "Restored $1. Refresh the browser with Ctrl+F5."
}

case "$(basename "$0")" in
  b2g-rollback*) if [ $# -eq 0 ]; then list; else restore "$1"; fi ;;
  *) backup "${1:-manual}" ;;
esac
B2GBACKUP
chmod 755 /usr/local/bin/b2g-backup; ln -sf /usr/local/bin/b2g-backup /usr/local/bin/b2g-rollback
if [ -d "$ROOT/app" ]; then ROOT="$ROOT" /usr/local/bin/b2g-backup before-install; else echo "   first install, nothing to save"; fi

log "Unpacking the app into $ROOT"
TMP=$(mktemp -d)
cat "$SRC"/parts/b2g-app.zip.* > "$TMP/b2g-app.zip"
rm -rf "$ROOT/app"; mkdir -p "$ROOT"
unzip -q "$TMP/b2g-app.zip" -d "$ROOT"
[ -f "$ROOT/app/www/app/index.html" ] || { echo "Unpack failed: $ROOT/app/www/app/index.html not found"; exit 1; }

log "Applying the India edition"
unzip -q "$SRC/b2g-india-edition-patch.zip" -d "$TMP/india"
cp -a "$ROOT/app/www" "$ROOT/app/www-india"
cp -a "$TMP/india/www-india/." "$ROOT/app/www-india/"
tr -d '\r' < "$TMP/india/removed-files.txt" | tr '\\' '/' | while read -r f; do [ -n "$f" ] && rm -f "$ROOT/app/www-india/$f"; done
cp -a "$TMP/india/backend/." "$ROOT/app/"
rm -rf "$ROOT/app/mock3p"; cp -a "$TMP/india/mock3p" "$ROOT/app/mock3p"
if [ -d "$TMP/india/uploads" ]; then mkdir -p "$ROOT/app/b2g_filemst/api/web/uploads"; cp -a "$TMP/india/uploads/." "$ROOT/app/b2g_filemst/api/web/uploads/"; fi   # demo logos, photos
rm -rf "$ROOT/app/www" "$ROOT/app/docs"

log "Removing third-party credentials from the backend code"
python3 "$SRC/scrub_secrets.py" "$ROOT/app"

log "Database"
cat > /etc/mysql/mariadb.conf.d/99-b2g.cnf <<'EOF'
[mysqld]
bind-address = 127.0.0.1
sql_mode = NO_ENGINE_SUBSTITUTION
innodb_buffer_pool_size = 256M
max_connections = 200
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
EOF
svc restart mariadb
for i in $(seq 1 30); do mysqladmin ping >/dev/null 2>&1 && break; sleep 1; done
for db in $(mysql -N -e "SHOW DATABASES LIKE 'bgi\_b2g\_%'"); do mysql -e "DROP DATABASE \`$db\`"; done
echo "   loading the India demo database (about a minute)"
mysql --default-character-set=utf8mb4 < "$TMP/india/db/b2g_seed_india.sql"
DBPW="${DBPW:-$(python3 -c 'import secrets;print(secrets.token_hex(16))')}"
mysql -e "CREATE USER IF NOT EXISTS 'b2g'@'localhost' IDENTIFIED BY '$DBPW'; ALTER USER 'b2g'@'localhost' IDENTIFIED BY '$DBPW';
          CREATE USER IF NOT EXISTS 'b2g'@'127.0.0.1' IDENTIFIED BY '$DBPW'; ALTER USER 'b2g'@'127.0.0.1' IDENTIFIED BY '$DBPW';
          GRANT ALL ON \`bgi\_b2g\_%\`.* TO 'b2g'@'localhost'; GRANT ALL ON \`bgi\_b2g\_%\`.* TO 'b2g'@'127.0.0.1'; FLUSH PRIVILEGES;"
echo "   India companies: $(mysql -N -e 'select count(*) from bgi_b2g_reg_dev.membercompanymst_tbl where mcm_countrymst_fk=104')"

log "PHP workers ($WORKERS)"
G="http://127.0.0.1:$PORT"
mkdir -p "$(dirname "$SOCK")"
cat > /etc/php/8.3/fpm/pool.d/b2g.conf <<EOF
[b2g]
user = www-data
group = www-data
listen = $SOCK
listen.owner = www-data
listen.group = www-data
pm = static
pm.max_children = $WORKERS
pm.max_requests = 500
request_terminate_timeout = 300
clear_env = yes
php_admin_value[memory_limit] = 512M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_value[max_execution_time] = 300
env[APP_ENV] = local
env[DB_HOST] = 127.0.0.1
env[DB_PORT] = 3306
env[DB_USER] = b2g
env[DB_PASSWORD] = $DBPW
env[FILEMST_DB_NAME] = bgi_b2g_reg_dev
env[STORAGE_DRIVER] = local
env[HOME_COUNTRY_PK] = 104
env[HOME_COUNTRY_NAME] = India
env[HOME_CURRENCY] = INR
env[MOCK3P_BASE_URL] = $G/mock3p
env[TEDDY_KEY_FILE] = /etc/b2g/anthropic.key
env[AI_SUGGESTIONS_URL] = "$G/mock3p/strategic/api/v1/suggest-questions?compact=true"
env[WEBSITE_SCRAPE_URL] = $G/mock3p/scrape/website
env[MICRO_BASE_URL] = $G/micro/web
env[MICRO_API_BASE_URL] = $G/micro/api
env[USERMGMT_BASE_URL] = $G/usermg/web
env[PROFILEMGMT_BASE_URL] = $G/profilemg/web
env[MAIL_BASE_URL] = $G/mail/web
env[GMARKETAPI_BASE_URL] = $G/gmarketapi/web
env[CENGEX_BASE_URL] = $G/cengex/web
env[DIGITALL_BASE_URL] = $G/digitall/web
env[BSGPACK_BASE_URL] = $G/micro
env[ZOHOAPI_BASE_URL] = $G/zohoapi/web
env[CONFMGMT_BASE_URL] = $G/configmg/web
env[FILEMST_BASE_URL] = $G/filemst/api
env[SUBPAYMENT_BASE_URL] = $G/subpayment/web
EOF
chown -R www-data:www-data "$ROOT/app"
# Teddy AI: the Claude API key lives outside the app folder so reinstalling keeps it. Never in the patch or the database.
mkdir -p /etc/b2g
if [ -n "${ANTHROPIC_API_KEY:-}" ]; then printf '%s\n' "$ANTHROPIC_API_KEY" > /etc/b2g/anthropic.key; fi
if [ -f /etc/b2g/anthropic.key ]; then chown root:www-data /etc/b2g/anthropic.key; chmod 640 /etc/b2g/anthropic.key; fi
svc restart php8.3-fpm

log "Web server on port $PORT"
A="$ROOT/app"
BACKENDS="micro/web:b2g_micro/web micro/api:b2g_micro/api usermg/web:b2g_usermgmt/web api:b2g_usermgmt/api profilemg/web:b2g_profilemgmt/web
configmg/web:b2g_confmgmt/web digitall/web:b2g_digitall/web filemst/api:b2g_filemst/api filemst/backend:b2g_filemst/backend/web
filemst/storage:b2g_filemst/storage cengex/web:b2g_cengex/web gmarketapi/web:b2g_gmarketapi/web mail/web:b2g_mail/web
subpayment/web:b2g_subpayment/web zohoapi/web:b2g_zohoapi/web nibedatamgnr/web:nibe_datamanager/web"
CONF=/etc/nginx/sites-available/$NGINX_SITE
printf '%s:%s\n' "$GATE_USER" "$(openssl passwd -apr1 "$GATE_PASS")" > /etc/nginx/b2g-gate.htpasswd
chown root:www-data /etc/nginx/b2g-gate.htpasswd; chmod 640 /etc/nginx/b2g-gate.htpasswd
GATE_TOKEN=$(printf '%s:%s:b2g-gate-v1' "$GATE_USER" "$GATE_PASS" | sha256sum | cut -c1-40)
{
  echo "# b2g India edition (generated by install-b2g-aws.sh)"
  p=$IPORT
  for b in $BACKENDS; do
    echo "server { listen 127.0.0.1:$p; root $A/${b#*:}; index index.php;"
    echo "  location / { try_files \$uri \$uri/ /index.php\$is_args\$args; }"
    echo "  location ~ \\.php\$ { try_files \$uri =404; include fastcgi_params; fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; fastcgi_read_timeout 300s; fastcgi_pass unix:$SOCK; } }"
    p=$((p+1))
  done
  # Login gate: the browser asks for GATE_USER/GATE_PASS once at /b2g-gate/, which sets a cookie.
  # Every other path needs that cookie, except calls the backends make to each other from this server.
  echo "map \$cookie_b2g_gate \$gate_cookie_ok { default 0; \"$GATE_TOKEN\" 1; }"
  echo "geo \$gate_is_local { default 0; 127.0.0.1 1; ::1 1; }"
  echo "map \$uri \$gate_exempt { default 0; ~^/b2g-gate/ 1; /healthz 1; /favicon.ico 1; }"
  echo "map \"\$gate_cookie_ok\$gate_is_local\$gate_exempt\" \$gate_pass { \"000\" 0; default 1; }"
  echo "map \$arg_r \$gate_dest { default /app/; ~^/[A-Za-z0-9][A-Za-z0-9/_.-]*\$ \$arg_r; }"
  echo "server {"
  echo "  listen $PORT default_server;"
  echo "  if (\$gate_pass = 0) { return 302 /b2g-gate/?r=\$uri; }"
  # try_files runs after the password check (a plain return would skip it), then the named location sets the cookie
  echo "  location = /b2g-gate/ { auth_basic \"blu2green\"; auth_basic_user_file /etc/nginx/b2g-gate.htpasswd; try_files /b2g-gate-none @b2g_gate_ok; }"
  echo "  location @b2g_gate_ok { add_header Set-Cookie \"b2g_gate=$GATE_TOKEN; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax\" always; return 302 \$gate_dest; }"
  echo "  client_max_body_size 64M; proxy_read_timeout 300s;"
  echo "  gzip on; gzip_types text/css application/javascript application/json image/svg+xml text/plain;"
  echo "  location = /healthz { return 200 'ok'; }"
  echo "  location /mock3p/ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $A/mock3p/index.php; fastcgi_read_timeout 120s; fastcgi_buffering off; fastcgi_pass unix:$SOCK; }"
  # security: never serve the file manager's own folders (logs, config, code, uploaded documents: those open only through the signed-in viewer) or stray files; BGI's unused OpenAI module is switched off
  echo "  location ~ ^/filemst/(api|backend|storage)/(runtime|config|common|commonfunction|models|modules|views|vendor|venmaptemplate|tests|web)/ { return 404; }"
  echo "  location ~* \\.(log|sql|env|bak|ini|lock|yml|yaml|txt)\$ { return 404; }"
  echo "  location ^~ /filemst/api/chgpt/ { return 404; }"
  echo "  location /filemst/api/drv/drive/view { proxy_pass http://127.0.0.1:$((IPORT+7))/drv/drive/view; proxy_set_header Host \$host; proxy_set_header Cookie \"\"; }"
  p=$IPORT
  for b in $BACKENDS; do
    ck=""; [ "${b%%:*}" = "filemst/api" ] && ck=' proxy_set_header Cookie "";'   # the file manager fails on other services' session cookies
    echo "  location /${b%%:*}/ { proxy_pass http://127.0.0.1:$p/; proxy_set_header Host \$host; proxy_set_header X-Forwarded-Host \$http_host; proxy_set_header Authorization \$http_authorization; proxy_buffering off;$ck }"
    p=$((p+1))
  done
  echo "  location /app/ { alias $A/www-india/app/; try_files \$uri \$uri/ /app/index.html; }"
  echo "  location /market/ { alias $A/www-india/market/; try_files \$uri \$uri/ /market/index.html; }"
  echo "  location /filems/ { alias $A/www-india/filems/; try_files \$uri \$uri/ /filems/index.html; }"
  echo "  location /nibe/ { alias $A/www-india/nibe/; try_files \$uri \$uri/ /nibe/index.html; }"
  echo "  location / { root $A/www-india/site; try_files \$uri \$uri/ /index.html; }"
  echo "}"
} > "$CONF"
ln -sf "$CONF" /etc/nginx/sites-enabled/$NGINX_SITE
[ "$PORT" = 80 ] && rm -f /etc/nginx/sites-enabled/default
nginx -t
svc restart nginx
rm -rf "$TMP"

log "Checking"
sleep 2
code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT/app/")
api=$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:$PORT/usermg/web/lng/login/check-user" -H 'Content-Type: application/json' -d '{"emailOrSupplierCode":"company.india@b2g-demo.test"}')
echo "   app page: $code   login service: $api   (both should be 200)"
ai=$(curl -s -o /dev/null -m 10 -w '%{http_code}' https://api.anthropic.com/v1/messages || true)
if [ -s /etc/b2g/anthropic.key ]; then k="key installed"; else k="NO KEY: run  sudo bash -c 'read -s -p \"Claude API key: \" K; printf \"%s\\n\" \"\$K\" > /etc/b2g/anthropic.key; chown root:www-data /etc/b2g/anthropic.key; chmod 640 /etc/b2g/anthropic.key'"; fi
echo "   Teddy AI: api.anthropic.com reachable: $( [ "$ai" = 000 ] && echo NO || echo yes ) ($ai)   $k"
cat > /usr/local/bin/b2g-teddy-check <<TEDDYEOF
#!/bin/bash
# Live check of Teddy chat with the real AI (a short test conversation as the demo company, deleted afterwards)
set -a; eval "\$(sed -n 's/^env\[\([A-Z_0-9]*\)\] = \(.*\)\$/\1=\2/p' /etc/php/8.3/fpm/pool.d/b2g.conf)"; set +a
cd $A/b2g_digitall && timeout 300 sudo -E -u www-data php teddy-check.php "\${1:-company.india@b2g-demo.test}" "\${2:-en}"
TEDDYEOF
chmod 755 /usr/local/bin/b2g-teddy-check
if [ -s /etc/b2g/anthropic.key ] && [ "$ai" != 000 ]; then log "Teddy live check (real AI, about a minute)"; /usr/local/bin/b2g-teddy-check || echo "   Teddy check did not finish; run: sudo b2g-teddy-check"; fi
IP=$(TOKEN=$(curl -s -m 2 -X PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' || true); \
     curl -s -m 2 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4 || true)
echo "$IP" | grep -Eq '^[0-9]+(\.[0-9]+){3}$' || IP=""
echo
echo "DONE. Open: http://${IP:-<server public IP>}$( [ "$PORT" = 80 ] || echo ":$PORT" )/app/"
echo "Logins: company.india@b2g-demo.test (and consultant/auditor/sectorpartner/buyer .india), password Demo@b2g2026, PIN 246810"
echo "Super admin: admin.india@b2g-demo.test at /app/auth/loginadmin"
