#!/bin/bash
set -euo pipefail

# APISIX runtime setup for Cloudron:
#   1. persist the Admin API key (rotating it would break automation clients)
#   2. relocate the APISIX runtime tree to /app/data - Cloudron mounts the
#      container rootfs READ-ONLY (writable: /app/data, /tmp, /run), so the
#      generated config.yaml, nginx.conf, logs and sockets must live there
#   3. start the embedded single-node etcd (state under /app/data/etcd,
#      survives restarts and backups)
#   4. regenerate config.yaml on EVERY start, then apisix init + init_etcd
#   5. seed the /healthz route once (cosmetic - Cloudron accepts 4xx)
#   6. exec openresty in the foreground with the /app/data prefix
#
# The Admin API listens on 127.0.0.1:9180 ONLY and is never published;
# reach it via `cloudron exec` with the key in /app/data/.admin_key.

DATA_DIR="/app/data"
ETCD_DIR="${DATA_DIR}/etcd"
ADMIN_KEY_FILE="${DATA_DIR}/.admin_key"
ETCD_ENDPOINT="http://127.0.0.1:2379"

PROXY_PORT="${CLOUDRON_APP_PORT:-9080}"

# writable runtime tree (container rootfs is read-only on Cloudron).
# The apisix CLI derives its install dir from the script path and writes
# nginx.conf there, so the lua CLI tree + deps are copied to /app/data and
# invoked from the copy (~62 MB, once per start).
export APISIX_PREFIX="${DATA_DIR}/apisix"
CONF="${APISIX_PREFIX}/conf/config.yaml"
mkdir -p "${APISIX_PREFIX}/conf" "${APISIX_PREFIX}/logs"
# re-copy on every start so upgrades never run a stale tree
rm -rf "${APISIX_PREFIX}/apisix" "${APISIX_PREFIX}/deps"
cp -a /usr/local/apisix/apisix "${APISIX_PREFIX}/"
cp -a /usr/local/apisix/deps "${APISIX_PREFIX}/"
# the CLI hardcodes apisix_home = /usr/local/apisix (read-only); repoint the
# copy at the writable prefix so nginx.conf and deps resolve under /app/data
grep -rl '/usr/local/apisix' "${APISIX_PREFIX}/apisix" \
  | xargs sed -i "s|/usr/local/apisix|${APISIX_PREFIX}|g"
# the CLI looks for its config templates under $APISIX_PREFIX/conf
cp -a /usr/local/apisix/conf/. "${APISIX_PREFIX}/conf/"
APISIX_CLI="/usr/local/openresty/luajit/bin/luajit ${APISIX_PREFIX}/apisix/cli/apisix.lua"

# --- 1. persistent Admin API key ---------------------------------------------
if [[ ! -s "${ADMIN_KEY_FILE}" ]]; then
  ( umask 077; head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' > "${ADMIN_KEY_FILE}" )
  echo "Generated new Admin API key at ${ADMIN_KEY_FILE}"
fi
ADMIN_KEY="$(cat "${ADMIN_KEY_FILE}")"

# --- 2. embedded etcd ----------------------------------------------------------
mkdir -p "${ETCD_DIR}"
# --initial-cluster is ignored on restarts from an existing data-dir.
etcd \
  --name default \
  --data-dir "${ETCD_DIR}" \
  --listen-client-urls "http://127.0.0.1:2379" \
  --advertise-client-urls "http://127.0.0.1:2379" \
  --listen-peer-urls "http://127.0.0.1:2380" \
  --initial-advertise-peer-urls "http://127.0.0.1:2380" \
  --initial-cluster "default=http://127.0.0.1:2380" \
  --auto-compaction-retention=1h &

echo "Waiting for embedded etcd at ${ETCD_ENDPOINT} ..."
for i in $(seq 1 30); do
  if etcdctl --endpoints="${ETCD_ENDPOINT}" endpoint health >/dev/null 2>&1; then
    echo "etcd is up"
    break
  fi
  if [[ "$i" -eq 30 ]]; then
    echo "etcd failed to become healthy" >&2
    exit 1
  fi
  sleep 1
done

# --- 3. generated config (rewritten on every start) -----------------------------
cat > "${CONF}" <<EOF
# Generated by start.sh on every boot - manual edits will be lost.
# Data-plane routes live in the embedded etcd (persisted under
# /app/data/etcd) and are managed through the Admin API.

deployment:
  role: traditional
  role_traditional:
    config_provider: etcd
  etcd:
    host:
      - "${ETCD_ENDPOINT}"
    prefix: /apisix
    timeout: 30
  admin:
    admin_listen:
      ip: 127.0.0.1
      port: 9180
    admin_key:
      - name: admin
        key: ${ADMIN_KEY}
        role: admin

apisix:
  node_listen:
    - ${PROXY_PORT}
  enable_ipv6: false
  ssl:
    # TLS terminates at the Cloudron platform proxy
    enable: false

nginx_config:
  error_log_level: warn
  worker_processes: auto
  http:
    # restore real client IPs from the Cloudron proxy's X-Forwarded-For
    real_ip_header: X-Forwarded-For
    real_ip_from:
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 192.168.0.0/16
      - 127.0.0.0/8
EOF

# --- 4. init + schema bootstrap -------------------------------------------------
${APISIX_CLI} init
${APISIX_CLI} init_etcd

# apisix init rewrites config.yaml (deployment timestamp). nginx runs workers
# as nobody (no `user` directive in the generated nginx.conf), so the config
# must stay world-readable within the container - acceptable because the
# Admin API itself listens on 127.0.0.1 only.
chmod 644 "${CONF}"
echo "Generated APISIX config at ${CONF} (proxy port ${PROXY_PORT})"

# --- 5. seed the health route once ----------------------------------------------
# Cloudron's healthcheck accepts 4xx, so it is not required for the platform -
# it gives https://<app-domain>/healthz a friendly answer instead of a bare 404.
if [[ -z "$(etcdctl --endpoints="${ETCD_ENDPOINT}" get /apisix/routes/healthz --print-value-only 2>/dev/null)" ]]; then
  # ngx.say is unavailable in the rewrite phase - a bare ngx.exit(200) is the
  # error-proof direct response (200, empty body).
  etcdctl --endpoints="${ETCD_ENDPOINT}" put /apisix/routes/healthz \
    '{"uri":"/healthz","id":"healthz","priority":100,"plugins":{"serverless-pre-function":{"phase":"rewrite","functions":["return function() ngx.exit(200) end"]}}}' > /dev/null \
    || echo "WARNING: seeding /healthz failed (cosmetic only, continuing)" >&2
  echo "Seeded /healthz route"
fi

# --- 6. run (foreground) ----------------------------------------------------------
echo "Starting APISIX ..."
rm -f "${APISIX_PREFIX}/conf/config_listen.sock" "${APISIX_PREFIX}/logs/worker_events.sock"
exec /usr/local/openresty/bin/openresty -p "${APISIX_PREFIX}" -g 'daemon off;'
