The 09-01 package referenced a nonexistent Cloudron etcd addon and an unpinned base image. Rewritten: digest-pinned apisix 3.18.0 with an embedded single-node etcd, loopback-only Admin API, and read-only rootfs support. Deployed live at apigw.knownelement.com (healthz 200). Umbrella: https://projects.knownelement.com/issues/632
149 lines
5.8 KiB
Bash
Executable File
149 lines
5.8 KiB
Bash
Executable File
#!/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;'
|