rpki/deploy/server/build-run.sh
2026-07-28 14:21:08 +08:00

293 lines
7.2 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
CALLER_TARGET_PLATFORM="${TARGET_PLATFORM:-${RPKI_RTR_TARGET_PLATFORM:-}}"
CALLER_IMAGE_ARCH="${IMAGE_ARCH:-${RPKI_RTR_IMAGE_ARCH:-}}"
CALLER_SERVER_IMAGE="${SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE:-}}"
load_env_defaults() {
local line key value
[[ -f "${SCRIPT_DIR}/.env" ]] || return
while IFS= read -r line || [[ -n "${line}" ]]; do
line="${line%$'\r'}"
[[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]] && continue
[[ "${line}" == *"="* ]] || continue
key="${line%%=*}"
value="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
[[ "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
if [[ -z "${!key+x}" ]]; then
export "${key}=${value}"
fi
done < "${SCRIPT_DIR}/.env"
}
detect_image_arch() {
case "$(uname -m)" in
x86_64|amd64) printf '%s\n' amd64 ;;
aarch64|arm64) printf '%s\n' arm64 ;;
*) die "unsupported host architecture '$(uname -m)'; set RPKI_RTR_IMAGE_ARCH=amd64 or arm64" ;;
esac
}
platform_for_image_arch() {
case "$1" in
amd64) printf '%s\n' linux/amd64 ;;
arm64) printf '%s\n' linux/arm64 ;;
*) die "unsupported image arch '$1', expected amd64 or arm64" ;;
esac
}
image_arch_from_platform() {
case "$1" in
linux/amd64|amd64) printf '%s\n' amd64 ;;
linux/arm64|arm64) printf '%s\n' arm64 ;;
*) return 1 ;;
esac
}
load_env_defaults
MODE="${MODE:-tcp}"
ACTION="up"
DETACH="${DETACH:-1}"
BUILD="${BUILD:-1}"
TARGET_PLATFORM="${CALLER_TARGET_PLATFORM}"
IMAGE_ARCH="${CALLER_IMAGE_ARCH}"
SERVER_IMAGE="${CALLER_SERVER_IMAGE}"
BUILDX_PUSH="${BUILDX_PUSH:-0}"
BUILDX_BUILDER="${BUILDX_BUILDER:-}"
SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}"
usage() {
cat <<'EOF'
Usage:
deploy/server/build-run.sh [action] [options]
Actions:
up Build and start server (default)
build Build server image with docker compose
buildx Build server image with docker buildx
rebuild Rebuild image without cache, then start server
down|stop Stop server compose stack
restart Stop, then build and start server
logs Follow server logs
ps Show server compose status
Options:
--mode MODE base|tcp|tls|ssh (default: tcp)
--platform VALUE Target platform for buildx (default: host architecture)
--image-arch VALUE
Image architecture suffix for default tag: amd64|arm64
--server-image TAG
Server image tag for buildx and compose
--push Push buildx image instead of loading it locally
--builder NAME buildx builder name
--skip-base-pull Do not pull Dockerfile base images before buildx
--foreground Run server in foreground
--no-build Start without --build
-h|--help Show this help
EOF
}
die() {
echo "error: $*" >&2
exit 1
}
has_compose() {
docker compose version >/dev/null 2>&1
}
has_buildx() {
docker buildx version >/dev/null 2>&1
}
pull_base_images() {
[[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return
local platform image
local -a platforms images
IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}"
images=(rust:1.89-bookworm debian:bookworm-slim)
for platform in "${platforms[@]}"; do
for image in "${images[@]}"; do
docker pull --platform "${platform}" "${image}"
done
done
}
compose_files() {
case "${MODE}" in
base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;;
tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;;
tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;;
ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;;
*) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;;
esac
}
compose_args() {
local -a args files
args=()
[[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env")
mapfile -t files < <(compose_files)
args+=("${files[@]}")
printf '%s\n' "${args[@]}"
}
compose_run() {
local -a args
mapfile -t args < <(compose_args)
docker compose "${args[@]}" "$@"
}
up_server() {
local -a up_args
up_args=(up)
[[ "${DETACH}" == "1" ]] && up_args+=(-d)
[[ "${BUILD}" == "1" ]] && up_args+=(--build)
compose_run "${up_args[@]}"
}
buildx_server() {
has_buildx || die "docker buildx is not available"
if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then
die "multi-platform buildx output requires --push"
fi
local -a output_args builder_args
[[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load)
[[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=()
pull_base_images
docker buildx build "${builder_args[@]}" \
--platform "${TARGET_PLATFORM}" \
-f "${SCRIPT_DIR}/Dockerfile" \
-t "${SERVER_IMAGE}" \
"${output_args[@]}" \
"${REPO_ROOT}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
up|build|buildx|rebuild|down|stop|restart|logs|ps)
ACTION="$1"
shift
;;
--mode)
[[ $# -ge 2 ]] || die "--mode requires a value"
MODE="$2"
shift 2
;;
--platform)
[[ $# -ge 2 ]] || die "--platform requires a value"
TARGET_PLATFORM="$2"
shift 2
;;
--image-arch)
[[ $# -ge 2 ]] || die "--image-arch requires a value"
IMAGE_ARCH="$2"
shift 2
;;
--server-image)
[[ $# -ge 2 ]] || die "--server-image requires a value"
SERVER_IMAGE="$2"
shift 2
;;
--push)
BUILDX_PUSH=1
shift
;;
--builder)
[[ $# -ge 2 ]] || die "--builder requires a value"
BUILDX_BUILDER="$2"
shift 2
;;
--skip-base-pull)
SKIP_BASE_IMAGE_PULL=1
shift
;;
--foreground)
DETACH=0
shift
;;
--no-build)
BUILD=0
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown argument '$1'"
;;
esac
done
if [[ -z "${TARGET_PLATFORM}" ]]; then
[[ -n "${IMAGE_ARCH}" ]] || IMAGE_ARCH="$(detect_image_arch)"
TARGET_PLATFORM="$(platform_for_image_arch "${IMAGE_ARCH}")"
elif [[ -z "${IMAGE_ARCH}" ]]; then
if [[ "${TARGET_PLATFORM}" != *,* ]] && IMAGE_ARCH="$(image_arch_from_platform "${TARGET_PLATFORM}")"; then
:
else
IMAGE_ARCH="$(detect_image_arch)"
fi
fi
if [[ -z "${SERVER_IMAGE}" ]]; then
if [[ "${TARGET_PLATFORM}" == *,* ]]; then
SERVER_IMAGE="${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}"
else
SERVER_IMAGE="${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${IMAGE_ARCH}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}"
fi
fi
export RPKI_RTR_SERVER_IMAGE="${SERVER_IMAGE}"
if [[ "${ACTION}" == "buildx" ]]; then
has_buildx || die "docker buildx is not available"
else
has_compose || die "docker compose is not available"
fi
cd "${REPO_ROOT}"
case "${ACTION}" in
up)
up_server
;;
build)
compose_run build
;;
buildx)
buildx_server
;;
rebuild)
compose_run build --no-cache
up_server
;;
down|stop)
compose_run down
;;
restart)
compose_run down
up_server
;;
logs)
compose_run logs -f
;;
ps)
compose_run ps
;;
esac