#!/usr/bin/env bash # 从 dist/linux-deb 产物生成可部署的 APT 仓库目录(repo/)。 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" DIST_ROOT="$REPO_ROOT/dist/linux-deb" OUTPUT_REPO_DIR="$REPO_ROOT/dist/repo" SUITE="${APT_REPO_SUITE:-v10}" COMPONENT="${APT_REPO_COMPONENT:-main}" ARCHES="${APT_REPO_ARCHES:-amd64 arm64}" GPG_KEY_ID="${APT_GPG_KEY_ID:-com.jgzy.product}" AUTO_CREATE_GPG_KEY="${APT_GPG_AUTO_CREATE:-1}" PUBLIC_KEY_ASC_NAME="${APT_PUBLIC_KEY_ASC_NAME:-zyyun-archive-keyring.asc}" PUBLIC_KEY_GPG_NAME="${APT_PUBLIC_KEY_GPG_NAME:-zyyun-archive-keyring.gpg}" usage() { cat <] [--suite ] [--component ] [项目名...] 环境变量: APT_GPG_KEY_ID GPG 签名 Key ID(默认: com.jgzy.product) APT_GPG_AUTO_CREATE 1/0。密钥不存在时自动执行 quick-gen-key(默认: 1) APT_REPO_SUITE 仓库 suite(默认: v10) APT_REPO_COMPONENT 仓库 component(默认: main) APT_REPO_ARCHES 仓库架构(默认: "amd64 arm64") EOF } PROJECTS=() while (($# > 0)); do case "$1" in --output-dir) OUTPUT_REPO_DIR="${2:-}" shift 2 ;; --suite) SUITE="${2:-}" shift 2 ;; --component) COMPONENT="${2:-}" shift 2 ;; --help|-h) usage exit 0 ;; --) shift break ;; -*) echo "错误: 未知参数 $1" >&2 exit 1 ;; *) PROJECTS+=("$1") shift ;; esac done if (($# > 0)); then PROJECTS+=("$@") fi if [[ ! -d "$DIST_ROOT" ]]; then echo "错误: 未找到 deb 产物目录 $DIST_ROOT" >&2 echo "请先执行 scripts/docker/run-build.sh 或 scripts/build-linux-deb-all.sh" >&2 exit 1 fi if ! command -v apt-ftparchive >/dev/null 2>&1; then echo "错误: 未安装 apt-ftparchive(请安装 apt-utils)" >&2 exit 1 fi if ! command -v gpg >/dev/null 2>&1; then echo "错误: 未安装 gpg(签名与公钥导出需要)" >&2 exit 1 fi ensure_signing_key() { local key="$1" local quick_gen_cmd=( gpg --batch --pinentry-mode loopback --passphrase "" --quick-gen-key "$key" rsa4096 sign 5y ) if gpg --list-secret-keys "$key" >/dev/null 2>&1; then return 0 fi if [[ "$AUTO_CREATE_GPG_KEY" != "1" ]]; then echo "错误: 未找到签名私钥: $key" >&2 echo "请先执行以下命令创建密钥,或设置 APT_GPG_AUTO_CREATE=1 自动创建:" >&2 echo " ${quick_gen_cmd[*]}" >&2 exit 1 fi echo "==> gpg key not found, generating: $key" "${quick_gen_cmd[@]}" } rm -rf "$OUTPUT_REPO_DIR" mkdir -p "$OUTPUT_REPO_DIR/pool/$COMPONENT" "$OUTPUT_REPO_DIR/dists/$SUITE/$COMPONENT" copy_project_debs() { local project="$1" local initial="${project:0:1}" local src_dir="$DIST_ROOT/$project" local dst_dir="$OUTPUT_REPO_DIR/pool/$COMPONENT/$initial/$project" if [[ ! -d "$src_dir" ]]; then echo "警告: 未找到项目产物目录 $src_dir,跳过" >&2 return 0 fi mkdir -p "$dst_dir" shopt -s nullglob local files=( "$src_dir"/amd64/*.deb "$src_dir"/arm64/*.deb ) shopt -u nullglob if (( ${#files[@]} == 0 )); then echo "警告: $src_dir 下没有 .deb 文件,跳过" >&2 return 0 fi cp -v "${files[@]}" "$dst_dir/" } if ((${#PROJECTS[@]} == 0)); then shopt -s nullglob for project_path in "$DIST_ROOT"/*; do [[ -d "$project_path" ]] || continue PROJECTS+=( "$(basename "$project_path")" ) done shopt -u nullglob fi if ((${#PROJECTS[@]} == 0)); then echo "错误: 未发现任何可发布项目($DIST_ROOT 为空)" >&2 exit 1 fi ensure_signing_key "$GPG_KEY_ID" for project in "${PROJECTS[@]}"; do copy_project_debs "$project" done # 为每个架构生成独立 Packages(按文件名后缀筛选)。 for arch in $ARCHES; do out_dir="$OUTPUT_REPO_DIR/dists/$SUITE/$COMPONENT/binary-$arch" mkdir -p "$out_dir" tmp_pool="$OUTPUT_REPO_DIR/.tmp-pool-$arch" rm -rf "$tmp_pool" mkdir -p "$tmp_pool" shopt -s globstar nullglob arch_files=( "$OUTPUT_REPO_DIR"/pool/$COMPONENT/**/*.deb ) shopt -u globstar nullglob for deb_file in "${arch_files[@]}"; do base="$(basename "$deb_file")" # 兼容 xxx_amd64.deb / xxx_arm64.deb 命名 if [[ "$base" == *"_${arch}.deb" ]]; then rel="${deb_file#$OUTPUT_REPO_DIR/pool/$COMPONENT/}" mkdir -p "$tmp_pool/$(dirname "$rel")" cp -f "$deb_file" "$tmp_pool/$rel" fi done apt-ftparchive packages "$tmp_pool" > "$out_dir/Packages" # apt-ftparchive 会把临时目录的绝对路径写入 Filename,客户端无法下载;改为相对仓库根的 pool/... 路径。 sed -i "s|^Filename: .*\\.tmp-pool-${arch}/|Filename: pool/${COMPONENT}/|" "$out_dir/Packages" gzip -kf "$out_dir/Packages" rm -rf "$tmp_pool" done apt-ftparchive \ -o APT::FTPArchive::Release::Suite="$SUITE" \ -o APT::FTPArchive::Release::Codename="$SUITE" \ -o APT::FTPArchive::Release::Components="$COMPONENT" \ -o APT::FTPArchive::Release::Architectures="$ARCHES" \ release "$OUTPUT_REPO_DIR/dists/$SUITE" > "$OUTPUT_REPO_DIR/dists/$SUITE/Release" gpg --default-key "$GPG_KEY_ID" -abs \ -o "$OUTPUT_REPO_DIR/dists/$SUITE/Release.gpg" \ "$OUTPUT_REPO_DIR/dists/$SUITE/Release" gpg --default-key "$GPG_KEY_ID" --clearsign \ -o "$OUTPUT_REPO_DIR/dists/$SUITE/InRelease" \ "$OUTPUT_REPO_DIR/dists/$SUITE/Release" gpg --armor --export "$GPG_KEY_ID" > "$OUTPUT_REPO_DIR/$PUBLIC_KEY_ASC_NAME" gpg --export "$GPG_KEY_ID" > "$OUTPUT_REPO_DIR/$PUBLIC_KEY_GPG_NAME" SIGN_HINT="已签名(KEY: $GPG_KEY_ID)" echo "" echo "==> APT 仓库目录生成完成" echo " 输出目录: $OUTPUT_REPO_DIR" echo " Suite: $SUITE, Component: $COMPONENT, Architectures: $ARCHES" echo " 签名状态: $SIGN_HINT" echo " 公钥文件: $OUTPUT_REPO_DIR/$PUBLIC_KEY_ASC_NAME" echo " $OUTPUT_REPO_DIR/$PUBLIC_KEY_GPG_NAME" echo " 可直接将 dist/repo 打包上传到仓库机。"