From d0924017f4f75377a0870683fa0b813d0bb95126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:09:04 +0800 Subject: [PATCH 1/3] fix: write valid image index metadata --- scripts/offline_build_lib.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/offline_build_lib.sh b/scripts/offline_build_lib.sh index fc47ffc..4ea3ac9 100644 --- a/scripts/offline_build_lib.sh +++ b/scripts/offline_build_lib.sh @@ -87,12 +87,12 @@ build_and_save_images() { docker pull --platform "linux/$ARCH" "$REDIS_LOCAL_REF" docker pull --platform "linux/$ARCH" "$POSTGRES_LOCAL_REF" - cat > "$payload_dir/images/image-index.tsv" < "$payload_dir/images/image-index.tsv" log "saving images to payload/images/images.tar" docker save -o "$payload_dir/images/images.tar" "$APP_LOCAL_REF" "$REDIS_LOCAL_REF" "$POSTGRES_LOCAL_REF" @@ -109,7 +109,7 @@ EOF_INFO copy_payload_files() { local package_dir="$1" local payload_dir="$2" - mkdir -p "$payload_dir" + mkdir -p "$payload_dir/images" cp "$package_dir/install.sh" "$payload_dir/install.sh" chmod +x "$payload_dir/install.sh" if [[ -d "$package_dir/templates" ]]; then cp -a "$package_dir/templates" "$payload_dir/"; fi From 65947ca2def2934da417286cc11cbdf4ebde5bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:09:27 +0800 Subject: [PATCH 2/3] fix: validate compose image index before rendering --- packages/compose/install.sh | 44 ++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/compose/install.sh b/packages/compose/install.sh index e52f038..6b5f77f 100644 --- a/packages/compose/install.sh +++ b/packages/compose/install.sh @@ -14,6 +14,12 @@ POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-new-api-change-me}" REDIS_PASSWORD="${REDIS_PASSWORD:-new-api-change-me}" SESSION_SECRET="${SESSION_SECRET:-change-me-please}" TZ_VALUE="${TZ:-Asia/Shanghai}" +NEW_API_LOCAL="" +NEW_API_TARGET="" +REDIS_LOCAL="" +REDIS_TARGET="" +POSTGRES_LOCAL="" +POSTGRES_TARGET="" log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; } @@ -65,19 +71,49 @@ parse_args() { confirm() { [[ "$YES" == "true" ]] && return 0; read -r -p "$1 [y/N] " ans; [[ "$ans" == y || "$ans" == Y ]]; } script_dir() { cd "$(dirname "${BASH_SOURCE[0]}")" && pwd; } trim_slash() { printf '%s' "$1" | sed 's#/*$##'; } +trim_ws() { printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'; } compose_cmd() { if docker compose version >/dev/null 2>&1; then echo "docker compose"; elif command -v docker-compose >/dev/null 2>&1; then echo "docker-compose"; else die "missing docker compose"; fi; } -target_for() { if [[ -n "$REGISTRY" ]]; then printf '%s/%s' "$(trim_slash "$REGISTRY")" "$1"; else printf '%s' "$2"; fi; } +target_for() { + local target_ref="$1" + local local_ref="$2" + [[ -n "$target_ref" ]] || target_ref="$local_ref" + if [[ -n "$REGISTRY" ]]; then printf '%s/%s' "$(trim_slash "$REGISTRY")" "$target_ref"; else printf '%s' "$local_ref"; fi +} +validate_image_map() { + local f="$1" + local missing="" + [[ -n "$NEW_API_LOCAL" && -n "$NEW_API_TARGET" ]] || missing="${missing} new-api" + [[ -n "$REDIS_LOCAL" && -n "$REDIS_TARGET" ]] || missing="${missing} redis" + [[ -n "$POSTGRES_LOCAL" && -n "$POSTGRES_TARGET" ]] || missing="${missing} postgres" + if [[ -n "$missing" ]]; then + printf '[ERROR] invalid image index: %s\n' "$f" >&2 + printf '[ERROR] missing component(s):%s\n' "$missing" >&2 + printf '[ERROR] first lines of image-index.tsv:\n' >&2 + sed -n '1,20p' "$f" >&2 || true + exit 1 + fi +} load_image_map() { local f="$(script_dir)/images/image-index.tsv" + local line component local_ref target_ref _extra + NEW_API_LOCAL=""; NEW_API_TARGET=""; REDIS_LOCAL=""; REDIS_TARGET=""; POSTGRES_LOCAL=""; POSTGRES_TARGET="" [[ -f "$f" ]] || die "missing image index: $f" - while IFS=$'\t' read -r component local_ref target_ref; do - [[ "$component" == component || -z "$component" ]] && continue + while IFS= read -r line || [[ -n "$line" ]]; do + [[ -z "$(trim_ws "$line")" ]] && continue + [[ "$(trim_ws "$line")" == \#* ]] && continue + line="${line//\\t/$'\t'}" + IFS=$'\t' read -r component local_ref target_ref _extra <<< "$line" + component="$(trim_ws "${component:-}")" + local_ref="$(trim_ws "${local_ref:-}")" + target_ref="$(trim_ws "${target_ref:-}")" + [[ "$component" == "component" ]] && continue case "$component" in new-api) NEW_API_LOCAL="$local_ref"; NEW_API_TARGET="$(target_for "$target_ref" "$local_ref")" ;; redis) REDIS_LOCAL="$local_ref"; REDIS_TARGET="$(target_for "$target_ref" "$local_ref")" ;; postgres) POSTGRES_LOCAL="$local_ref"; POSTGRES_TARGET="$(target_for "$target_ref" "$local_ref")" ;; esac done < "$f" + validate_image_map "$f" } prepare_images() { [[ "$SKIP_IMAGE_PREPARE" == true ]] && { log "skip image prepare"; return; } @@ -116,6 +152,8 @@ do_install() { need_cmd docker; load_image_map echo "Install dir: $INSTALL_DIR" echo "New-API image: $NEW_API_TARGET" + echo "Redis image: $REDIS_TARGET" + echo "Postgres image: $POSTGRES_TARGET" confirm "Continue?" || die "cancelled" prepare_images render_compose From 03e492baef91df7853f86d95dc2ed9a4956babc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E7=8E=89=E9=B9=8F?= <51024420+yuanyp8@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:09:56 +0800 Subject: [PATCH 3/3] fix: validate k8s image index before rendering --- packages/k8s/install.sh | 46 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/k8s/install.sh b/packages/k8s/install.sh index 495f08b..90ddff2 100644 --- a/packages/k8s/install.sh +++ b/packages/k8s/install.sh @@ -16,6 +16,12 @@ REDIS_PASSWORD="${REDIS_PASSWORD:-new-api-change-me}" SESSION_SECRET="${SESSION_SECRET:-change-me-please}" TZ_VALUE="${TZ:-Asia/Shanghai}" POSTGRES_STORAGE_SIZE="${POSTGRES_STORAGE_SIZE:-10Gi}" +NEW_API_LOCAL="" +NEW_API_TARGET="" +REDIS_LOCAL="" +REDIS_TARGET="" +POSTGRES_LOCAL="" +POSTGRES_TARGET="" log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; } @@ -71,18 +77,48 @@ parse_args() { confirm() { [[ "$YES" == true ]] && return 0; read -r -p "$1 [y/N] " ans; [[ "$ans" == y || "$ans" == Y ]]; } script_dir() { cd "$(dirname "${BASH_SOURCE[0]}")" && pwd; } trim_slash() { printf '%s' "$1" | sed 's#/*$##'; } -target_for() { if [[ -n "$REGISTRY" ]]; then printf '%s/%s' "$(trim_slash "$REGISTRY")" "$1"; else printf '%s' "$2"; fi; } +trim_ws() { printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'; } +target_for() { + local target_ref="$1" + local local_ref="$2" + [[ -n "$target_ref" ]] || target_ref="$local_ref" + if [[ -n "$REGISTRY" ]]; then printf '%s/%s' "$(trim_slash "$REGISTRY")" "$target_ref"; else printf '%s' "$local_ref"; fi +} +validate_image_map() { + local f="$1" + local missing="" + [[ -n "$NEW_API_LOCAL" && -n "$NEW_API_TARGET" ]] || missing="${missing} new-api" + [[ -n "$REDIS_LOCAL" && -n "$REDIS_TARGET" ]] || missing="${missing} redis" + [[ -n "$POSTGRES_LOCAL" && -n "$POSTGRES_TARGET" ]] || missing="${missing} postgres" + if [[ -n "$missing" ]]; then + printf '[ERROR] invalid image index: %s\n' "$f" >&2 + printf '[ERROR] missing component(s):%s\n' "$missing" >&2 + printf '[ERROR] first lines of image-index.tsv:\n' >&2 + sed -n '1,20p' "$f" >&2 || true + exit 1 + fi +} load_image_map() { local f="$(script_dir)/images/image-index.tsv" + local line component local_ref target_ref _extra + NEW_API_LOCAL=""; NEW_API_TARGET=""; REDIS_LOCAL=""; REDIS_TARGET=""; POSTGRES_LOCAL=""; POSTGRES_TARGET="" [[ -f "$f" ]] || die "missing image index: $f" - while IFS=$'\t' read -r component local_ref target_ref; do - [[ "$component" == component || -z "$component" ]] && continue + while IFS= read -r line || [[ -n "$line" ]]; do + [[ -z "$(trim_ws "$line")" ]] && continue + [[ "$(trim_ws "$line")" == \#* ]] && continue + line="${line//\\t/$'\t'}" + IFS=$'\t' read -r component local_ref target_ref _extra <<< "$line" + component="$(trim_ws "${component:-}")" + local_ref="$(trim_ws "${local_ref:-}")" + target_ref="$(trim_ws "${target_ref:-}")" + [[ "$component" == "component" ]] && continue case "$component" in new-api) NEW_API_LOCAL="$local_ref"; NEW_API_TARGET="$(target_for "$target_ref" "$local_ref")" ;; redis) REDIS_LOCAL="$local_ref"; REDIS_TARGET="$(target_for "$target_ref" "$local_ref")" ;; postgres) POSTGRES_LOCAL="$local_ref"; POSTGRES_TARGET="$(target_for "$target_ref" "$local_ref")" ;; esac done < "$f" + validate_image_map "$f" } prepare_images() { [[ "$SKIP_IMAGE_PREPARE" == true ]] && { log "skip image prepare"; return; } @@ -128,6 +164,8 @@ do_install() { need_cmd kubectl; load_image_map echo "Namespace: $NAMESPACE" echo "New-API image: $NEW_API_TARGET" + echo "Redis image: $REDIS_TARGET" + echo "Postgres image: $POSTGRES_TARGET" confirm "Continue?" || die "cancelled" prepare_images manifest="$(render_manifest)" @@ -135,7 +173,7 @@ do_install() { log "done. kubectl get pods,svc,deploy,statefulset,pvc -n $NAMESPACE" } do_status() { need_cmd kubectl; load_image_map; printf 'new-api=%s\nredis=%s\npostgres=%s\n' "$NEW_API_TARGET" "$REDIS_TARGET" "$POSTGRES_TARGET"; kubectl get pods,svc,deploy,statefulset,pvc -n "$NAMESPACE" || true; } -do_uninstall() { need_cmd kubectl; manifest="$(render_manifest)"; confirm "Delete New-API Kubernetes resources?" || die "cancelled"; if [[ "$DANGER_DELETE_DATA" == true ]]; then kubectl delete -f "$manifest" --ignore-not-found=true; else kubectl delete deploy/new-api deploy/new-api-redis svc/new-api svc/new-api-redis svc/new-api-postgres secret/new-api-secret statefulset/new-api-postgres -n "$NAMESPACE" --ignore-not-found=true; fi; } +do_uninstall() { need_cmd kubectl; load_image_map; manifest="$(render_manifest)"; confirm "Delete New-API Kubernetes resources?" || die "cancelled"; if [[ "$DANGER_DELETE_DATA" == true ]]; then kubectl delete -f "$manifest" --ignore-not-found=true; else kubectl delete deploy/new-api deploy/new-api-redis svc/new-api svc/new-api-redis svc/new-api-postgres secret/new-api-secret statefulset/new-api-postgres -n "$NAMESPACE" --ignore-not-found=true; fi; } parse_args "$@" case "$ACTION" in