Ship TLS nginx for backoffice, keep HTTP bootstrap until certs exist, proxy /monitoring to sentry-relay, and tunnel server/edge Sentry through Finland.
180 lines
7.6 KiB
Bash
Executable File
180 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Build admin image on the Finland host, then load+run it on Iran.
|
||
#
|
||
# Architecture:
|
||
# - Finland (git.ghabilee.ir / act_runner): build + Telegram notify
|
||
# - Iran: runtime only (/opt/ghabilee-admin)
|
||
set -euo pipefail
|
||
|
||
: "${FINLAND_SSH_KEY:?}"
|
||
: "${FINLAND_HOST:?}"
|
||
: "${FINLAND_USER:?}"
|
||
: "${IRAN_SSH_KEY:?}"
|
||
: "${IRAN_HOST:?}"
|
||
: "${IRAN_USER:?}"
|
||
|
||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
cd "$ROOT"
|
||
|
||
IMAGE_TAG="${IMAGE_TAG:-ghabilee-admin:$(git rev-parse --short HEAD 2>/dev/null || date +%s)}"
|
||
FINLAND_APP="/opt/ghabilee-admin-ci"
|
||
IRAN_APP="/opt/ghabilee-admin"
|
||
|
||
install -m 700 -d "$HOME/.ssh"
|
||
printf '%s\n' "$FINLAND_SSH_KEY" > "$HOME/.ssh/finland_key"
|
||
printf '%s\n' "$IRAN_SSH_KEY" > "$HOME/.ssh/iran_key"
|
||
chmod 600 "$HOME/.ssh/finland_key" "$HOME/.ssh/iran_key"
|
||
trap 'rm -f "$HOME/.ssh/finland_key" "$HOME/.ssh/iran_key"' EXIT
|
||
|
||
FSSH=(ssh -i "$HOME/.ssh/finland_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new)
|
||
ISSH=(ssh -i "$HOME/.ssh/iran_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new)
|
||
FRSYNC=(-e "ssh -i $HOME/.ssh/finland_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new")
|
||
|
||
# --- build env from Iran (runtime .env holds NEXT_PUBLIC_* bake inputs) ---
|
||
"${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" "test -s ${IRAN_APP}/.env"
|
||
scp -i "$HOME/.ssh/iran_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
|
||
"${IRAN_USER}@${IRAN_HOST}:${IRAN_APP}/.env" .env.production
|
||
test -s .env.production
|
||
|
||
# --- sync sources + env to Finland build dir ---
|
||
"${FSSH[@]}" "${FINLAND_USER}@${FINLAND_HOST}" "install -d -m 0750 '${FINLAND_APP}/src'"
|
||
rsync -az --delete "${FRSYNC[@]}" \
|
||
--exclude '.git' \
|
||
--exclude 'node_modules' \
|
||
--exclude '.next' \
|
||
--exclude '.env' \
|
||
--exclude '.env.*' \
|
||
--exclude 'test-results' \
|
||
--exclude 'playwright-report' \
|
||
./ "${FINLAND_USER}@${FINLAND_HOST}:${FINLAND_APP}/src/"
|
||
|
||
scp -i "$HOME/.ssh/finland_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
|
||
.env.production "${FINLAND_USER}@${FINLAND_HOST}:${FINLAND_APP}/.env"
|
||
rm -f .env.production
|
||
|
||
# --- build on Finland (enough RAM; Telegram/git live here too) ---
|
||
"${FSSH[@]}" "${FINLAND_USER}@${FINLAND_HOST}" \
|
||
"IMAGE_TAG='${IMAGE_TAG}' APP_DIR='${FINLAND_APP}' SRC_DIR='${FINLAND_APP}/src' bash -s" <<'REMOTE'
|
||
set -euo pipefail
|
||
cd "$APP_DIR"
|
||
# noglob: backend-style cron values must never expand if present in .env
|
||
set -f
|
||
# shellcheck disable=SC1091
|
||
set -a
|
||
# shellcheck disable=SC1090
|
||
source "$APP_DIR/.env"
|
||
set +a
|
||
set +f
|
||
|
||
env_or_empty() { printf '%s' "${!1-}"; }
|
||
|
||
sanitize_api_proxy_target() {
|
||
local target
|
||
target="$(env_or_empty API_PROXY_TARGET)"
|
||
case "$target" in
|
||
*127.0.0.1*|*localhost*|*'::1'*) printf '' ;;
|
||
*) printf '%s' "$target" ;;
|
||
esac
|
||
}
|
||
|
||
# DOCKER_BUILDKIT keeps layer/build cache across deploys; never prune -af here.
|
||
export DOCKER_BUILDKIT=1
|
||
docker build --platform linux/amd64 -t "$IMAGE_TAG" \
|
||
--build-arg "NEXT_PUBLIC_API_URL=$(env_or_empty NEXT_PUBLIC_API_URL)" \
|
||
--build-arg "NEXT_PUBLIC_FILE_SERVER_URL=$(env_or_empty NEXT_PUBLIC_FILE_SERVER_URL)" \
|
||
--build-arg "MAP_API_KEY=$(env_or_empty MAP_API_KEY)" \
|
||
--build-arg "NEXT_PUBLIC_MAP_API_KEY=$(env_or_empty NEXT_PUBLIC_MAP_API_KEY)" \
|
||
--build-arg "NEXT_PUBLIC_VAPID_PUBLIC_KEY=$(env_or_empty NEXT_PUBLIC_VAPID_PUBLIC_KEY)" \
|
||
--build-arg "NEXT_PUBLIC_BASE_PATH=$(env_or_empty NEXT_PUBLIC_BASE_PATH)" \
|
||
--build-arg "NEXT_PUBLIC_SITE_URL=$(env_or_empty NEXT_PUBLIC_SITE_URL)" \
|
||
--build-arg "NEXT_PUBLIC_OBSERVABILITY_ENDPOINT=$(env_or_empty NEXT_PUBLIC_OBSERVABILITY_ENDPOINT)" \
|
||
--build-arg "NEXT_PUBLIC_SENTRY_DSN=$(env_or_empty NEXT_PUBLIC_SENTRY_DSN)" \
|
||
--build-arg "NEXT_PUBLIC_SENTRY_ENVIRONMENT=$(env_or_empty NEXT_PUBLIC_SENTRY_ENVIRONMENT)" \
|
||
--build-arg "NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$(env_or_empty NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE)" \
|
||
--build-arg "NEXT_PUBLIC_ARCAPTCHA_SITE_KEY=$(env_or_empty NEXT_PUBLIC_ARCAPTCHA_SITE_KEY)" \
|
||
--build-arg "SENTRY_AUTH_TOKEN=$(env_or_empty SENTRY_AUTH_TOKEN)" \
|
||
--build-arg "SENTRY_ORG=$(env_or_empty SENTRY_ORG)" \
|
||
--build-arg "SENTRY_PROJECT=$(env_or_empty SENTRY_PROJECT)" \
|
||
--build-arg "API_PROXY_TARGET=$(sanitize_api_proxy_target)" \
|
||
"$SRC_DIR"
|
||
|
||
# فقط dangling؛ prune -a/-af کش build را میکشد و هر deploy را cold میکند
|
||
docker image prune -f >/dev/null 2>&1 || true
|
||
echo "BUILT $IMAGE_TAG"
|
||
REMOTE
|
||
|
||
# --- stream image Finland → Iran, then compose up ---
|
||
"${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" "install -d -m 0750 '${IRAN_APP}'"
|
||
scp -i "$HOME/.ssh/iran_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
|
||
deploy/docker-compose.production.yml \
|
||
"${IRAN_USER}@${IRAN_HOST}:${IRAN_APP}/docker-compose.yml"
|
||
if [[ -f deploy/nginx/backoffice.conf ]]; then
|
||
NGINX_BACKOFFICE=deploy/nginx/backoffice.conf
|
||
if ! "${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" 'test -s /etc/letsencrypt/live/backoffice.ghabilee.ir/fullchain.pem'; then
|
||
if [[ -f deploy/nginx/backoffice-http-bootstrap.conf ]]; then
|
||
NGINX_BACKOFFICE=deploy/nginx/backoffice-http-bootstrap.conf
|
||
fi
|
||
fi
|
||
scp -i "$HOME/.ssh/iran_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
|
||
"$NGINX_BACKOFFICE" \
|
||
"${IRAN_USER}@${IRAN_HOST}:/etc/nginx/sites-available/backoffice"
|
||
"${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" '
|
||
ln -sfn /etc/nginx/sites-available/backoffice /etc/nginx/sites-enabled/backoffice
|
||
nginx -t && systemctl reload nginx
|
||
'
|
||
fi
|
||
|
||
# فشردهسازی transfer؛ zstd اگر روی هر دو host باشد، وگرنه gzip
|
||
TRANSFER_CODEC=gzip
|
||
if "${FSSH[@]}" "${FINLAND_USER}@${FINLAND_HOST}" 'command -v zstd >/dev/null 2>&1' \
|
||
&& "${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" 'command -v zstd >/dev/null 2>&1'; then
|
||
TRANSFER_CODEC=zstd
|
||
fi
|
||
|
||
echo "Transferring ${IMAGE_TAG} Finland → Iran (${TRANSFER_CODEC})..."
|
||
if [[ "$TRANSFER_CODEC" == zstd ]]; then
|
||
"${FSSH[@]}" "${FINLAND_USER}@${FINLAND_HOST}" "docker save '${IMAGE_TAG}' | zstd -T0 -3" \
|
||
| "${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" 'zstd -d | docker load'
|
||
else
|
||
"${FSSH[@]}" "${FINLAND_USER}@${FINLAND_HOST}" "docker save '${IMAGE_TAG}' | gzip -1" \
|
||
| "${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" 'gzip -d | docker load'
|
||
fi
|
||
|
||
"${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" \
|
||
"ADMIN_IMAGE='${IMAGE_TAG}' bash -s" <<'REMOTE'
|
||
set -euo pipefail
|
||
cd /opt/ghabilee-admin
|
||
test -s .env
|
||
ADMIN_IMAGE="$ADMIN_IMAGE" docker compose -f docker-compose.yml up -d --no-deps ghabilee-admin
|
||
for attempt in $(seq 1 36); do
|
||
health="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' ghabilee-admin 2>/dev/null || echo missing)"
|
||
echo "Admin health ${attempt}/36: ${health}"
|
||
if [ "$health" = healthy ]; then
|
||
# آزاد کردن دیسک بدون نابود کردن کش: فقط tagهای قدیمی admin که در حال استفاده نیستند
|
||
docker images --format '{{.Repository}}:{{.Tag}}' \
|
||
| awk -v keep="$ADMIN_IMAGE" '$0 ~ /^ghabilee-admin:/ && $0 != keep { print }' \
|
||
| while IFS= read -r old; do
|
||
docker rmi "$old" >/dev/null 2>&1 || true
|
||
done
|
||
docker image prune -f >/dev/null 2>&1 || true
|
||
echo "Deploy OK"
|
||
exit 0
|
||
fi
|
||
case "$health" in unhealthy|exited|dead|missing) exit 1 ;; esac
|
||
sleep 5
|
||
done
|
||
exit 1
|
||
REMOTE
|
||
|
||
# روی Finland هم tagهای قدیمی admin را بردار؛ BuildKit cache دستنخورده میماند
|
||
"${FSSH[@]}" "${FINLAND_USER}@${FINLAND_HOST}" \
|
||
"IMAGE_TAG='${IMAGE_TAG}' bash -s" <<'REMOTE'
|
||
set -euo pipefail
|
||
docker images --format '{{.Repository}}:{{.Tag}}' \
|
||
| awk -v keep="$IMAGE_TAG" '$0 ~ /^ghabilee-admin:/ && $0 != keep { print }' \
|
||
| while IFS= read -r old; do
|
||
docker rmi "$old" >/dev/null 2>&1 || true
|
||
done
|
||
docker image prune -f >/dev/null 2>&1 || true
|
||
REMOTE
|