diff --git a/config.sh b/config.sh index 1be820e..576f237 100644 --- a/config.sh +++ b/config.sh @@ -2,15 +2,27 @@ # 复制为 config.sh 并根据你的环境修改 # 交叉编译目标 -export TARGET_HOST="aarch64-linux-gnu" -export BUILD_HOST="x86_64-linux-gnu" +export TARGET_HOST="${TARGET_HOST:-aarch64-linux-gnu}" +export BUILD_HOST="${BUILD_HOST:-x86_64-linux-gnu}" # 安装前缀 -export PREFIX="/data/data/a.io.github.ewt45.winemulator/linbox/usr" +export PREFIX="${PREFIX:-/data/data/a.io.github.ewt45.winemulator/linbox/usr}" # pkg-config 路径(交叉编译时使用) -export PKG_CONFIG_LIBDIR="${PREFIX}/lib/pkgconfig:${PREFIX}/share/pkgconfig" -export PKG_CONFIG_SYSROOT_DIR="$(dirname "${PREFIX}")" +export PKG_CONFIG_LIBDIR="${PKG_CONFIG_LIBDIR:-${PREFIX}/lib/pkgconfig:${PREFIX}/share/pkgconfig}" +export PKG_CONFIG_SYSROOT_DIR="${PKG_CONFIG_SYSROOT_DIR:-$(dirname "${PREFIX}")}" # 传递给 make install 的额外参数 -export MAKE_INSTALL_PROGRAM='INSTALL_PROGRAM="/usr/bin/install -c"' +export MAKE_INSTALL_PROGRAM="${MAKE_INSTALL_PROGRAM:-INSTALL_PROGRAM=\"/usr/bin/install -c\"}" + +# 远程容器构建(可通过环境变量覆盖) +export BUILD_IN_CONTAINER="${BUILD_IN_CONTAINER:-0}" +export CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" +export CONTAINER_REMOTE="${CONTAINER_REMOTE:-bazzite@192.168.0.100}" +export CONTAINER_REMOTE_DIR="${CONTAINER_REMOTE_DIR:-/home/bazzite/Documents/rootfs-packages-build}" +export CONTAINER_IMAGE="${CONTAINER_IMAGE:-tuxwine-rootfs-builder:ubuntu24}" +export CONTAINER_PROXY_URL="${CONTAINER_PROXY_URL:-http://192.168.0.102:8080}" +export CONTAINER_PROXY_MODE="${CONTAINER_PROXY_MODE:-auto}" +export CONTAINER_NO_PROXY="${CONTAINER_NO_PROXY:-localhost,127.0.0.1}" +export CONTAINER_REBUILD_IMAGE="${CONTAINER_REBUILD_IMAGE:-0}" +# CONTAINER_SSH_PASSWORD 仅从运行环境读取,不要写入 config.sh。 diff --git a/docker/tuxwine-builder/Dockerfile b/docker/tuxwine-builder/Dockerfile new file mode 100644 index 0000000..d4e611e --- /dev/null +++ b/docker/tuxwine-builder/Dockerfile @@ -0,0 +1,61 @@ +FROM ubuntu:24.04 + +ARG http_proxy +ARG https_proxy +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG no_proxy +ARG NO_PROXY + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + autoconf \ + automake \ + autopoint \ + bash \ + binutils-aarch64-linux-gnu \ + bison \ + bzip2 \ + ca-certificates \ + cmake \ + curl \ + file \ + flex \ + g++ \ + g++-aarch64-linux-gnu \ + gawk \ + gcc \ + gcc-aarch64-linux-gnu \ + gettext \ + git \ + gperf \ + gzip \ + libtool \ + libwayland-bin \ + libwayland-dev \ + make \ + meson \ + ninja-build \ + patch \ + perl \ + pkg-config \ + python3 \ + python3-packaging \ + rsync \ + tar \ + texinfo \ + unzip \ + wget \ + xz-utils \ + && rm -rf /var/lib/apt/lists/* + +RUN printf '%s\n' \ + '#!/bin/sh' \ + 'exec pkg-config "$@"' \ + > /usr/local/bin/aarch64-linux-gnu-pkg-config \ + && chmod +x /usr/local/bin/aarch64-linux-gnu-pkg-config + +WORKDIR /work/repo +CMD ["/bin/bash"] diff --git a/lib/container.sh b/lib/container.sh new file mode 100644 index 0000000..5048768 --- /dev/null +++ b/lib/container.sh @@ -0,0 +1,285 @@ +# lib/container.sh +# Optional remote Docker delegation for reproducible Linux builds. + +container_truthy() { + case "${1:-}" in + 1|true|TRUE|yes|YES|on|ON) return 0 ;; + *) return 1 ;; + esac +} + +container_shell_join() { + local out="" + local arg + for arg in "$@"; do + printf -v arg "%q" "$arg" + out+="${out:+ }${arg}" + done + printf '%s' "$out" +} + +container_ssh_command_string() { + local ssh_opts=( + -o StrictHostKeyChecking=no + -o UserKnownHostsFile=/dev/null + -o LogLevel=ERROR + ) + + if [[ -n "${CONTAINER_SSH_PASSWORD:-}" ]]; then + command -v sshpass >/dev/null 2>&1 || error "BUILD_IN_CONTAINER 需要 sshpass 或预先配置 SSH key" + container_shell_join sshpass -e ssh "${ssh_opts[@]}" + else + container_shell_join ssh "${ssh_opts[@]}" + fi +} + +container_prepare_ssh_password() { + if [[ -n "${CONTAINER_SSH_PASSWORD:-}" ]]; then + command -v sshpass >/dev/null 2>&1 || error "BUILD_IN_CONTAINER 需要 sshpass 或预先配置 SSH key" + export SSHPASS="${CONTAINER_SSH_PASSWORD}" + fi +} + +container_ssh() { + local ssh_cmd + container_prepare_ssh_password + ssh_cmd="$(container_ssh_command_string)" + # shellcheck disable=SC2086 + $ssh_cmd "$@" +} + +container_require_local_tools() { + command -v rsync >/dev/null 2>&1 || error "BUILD_IN_CONTAINER 需要本机安装 rsync" + command -v ssh >/dev/null 2>&1 || error "BUILD_IN_CONTAINER 需要本机安装 ssh" +} + +container_remote_prepare() { + local remote="$1" + local remote_dir="$2" + local remote_command + remote_command="$(container_shell_join bash -s -- "$remote_dir")" + + container_ssh "$remote" "$remote_command" <<'REMOTE_SCRIPT' +set -euo pipefail +remote_dir="$1" +mkdir -p \ + "$remote_dir/repo" \ + "$remote_dir/downloads" \ + "$remote_dir/work" \ + "$remote_dir/output" \ + "$remote_dir/rootfs" +REMOTE_SCRIPT +} + +container_sync_repo() { + local remote="$1" + local remote_dir="$2" + local ssh_cmd + container_prepare_ssh_password + ssh_cmd="$(container_ssh_command_string)" + + info "同步项目到 ${remote}:${remote_dir}/repo" + rsync -az --delete \ + --exclude ".git/" \ + --exclude "downloads/" \ + --exclude "work/" \ + --exclude "output/" \ + -e "$ssh_cmd" \ + "${PROJECT_ROOT}/" \ + "${remote}:${remote_dir}/repo/" +} + +container_select_proxy() { + local remote="$1" + local mode="${CONTAINER_PROXY_MODE:-auto}" + local proxy_url="${CONTAINER_PROXY_URL:-}" + local test_url="${CONTAINER_PROXY_TEST_URL:-https://ftp.gnu.org/gnu/libc/}" + local test_command + local proxy_command + + CONTAINER_SELECTED_PROXY="" + + case "$mode" in + off) + return 0 + ;; + always) + [[ -n "$proxy_url" ]] || error "CONTAINER_PROXY_MODE=always 需要设置 CONTAINER_PROXY_URL" + info "强制启用代理: $proxy_url" + CONTAINER_SELECTED_PROXY="$proxy_url" + return 0 + ;; + auto) + test_command="$(container_shell_join timeout 12 curl -fsSI "$test_url") >/dev/null 2>&1" + if container_ssh "$remote" "$test_command"; then + info "远程网络直连可用,不启用代理" + return 0 + fi + + [[ -n "$proxy_url" ]] || { + warn "远程网络直连失败,但未设置 CONTAINER_PROXY_URL" + return 0 + } + + proxy_command="$(container_shell_join timeout 12 curl -x "$proxy_url" -fsSI "$test_url") >/dev/null 2>&1" + if container_ssh "$remote" "$proxy_command"; then + info "远程网络直连失败,启用代理: $proxy_url" + CONTAINER_SELECTED_PROXY="$proxy_url" + else + warn "远程直连和代理测试都失败,仍将不带代理运行" + fi + ;; + *) + error "CONTAINER_PROXY_MODE 仅支持 auto、always、off" + ;; + esac +} + +container_remote_run() { + local remote="$1" + local remote_dir="$2" + local runtime="$3" + local image="$4" + local proxy_url="$5" + shift 5 + + local prefix="${PREFIX:?PREFIX 未设置}" + local prefix_root + prefix_root="$(dirname "$prefix")" + local remote_args + local remote_command + + info "在远程 Docker 容器中构建: ${image}" + remote_args=( + bash -s -- + "$remote_dir" \ + "$runtime" \ + "$image" \ + "$prefix" \ + "$prefix_root" \ + "${TARGET_HOST:-aarch64-linux-gnu}" \ + "${BUILD_HOST:-x86_64-linux-gnu}" \ + "${PKG_CONFIG_LIBDIR:-${prefix}/lib/pkgconfig:${prefix}/share/pkgconfig}" \ + "${PKG_CONFIG_SYSROOT_DIR:-${prefix_root}}" \ + "${MAKE_INSTALL_PROGRAM:-}" \ + "$proxy_url" \ + "${CONTAINER_NO_PROXY:-localhost,127.0.0.1}" \ + "${CONTAINER_REBUILD_IMAGE:-0}" \ + "$@" + ) + remote_command="$(container_shell_join "${remote_args[@]}")" + + container_ssh "$remote" "$remote_command" <<'REMOTE_SCRIPT' +set -euo pipefail + +remote_dir="$1" +runtime="$2" +image="$3" +prefix="$4" +prefix_root="$5" +target_host="$6" +build_host="$7" +pkg_config_libdir="$8" +pkg_config_sysroot_dir="$9" +make_install_program="${10}" +proxy_url="${11}" +no_proxy="${12}" +rebuild_image="${13}" +shift 13 + +repo_dir="$remote_dir/repo" +downloads_dir="$remote_dir/downloads" +work_dir="$remote_dir/work" +output_dir="$remote_dir/output" +rootfs_dir="$remote_dir/rootfs" +home_dir="$work_dir/home" +dockerfile="$repo_dir/docker/tuxwine-builder/Dockerfile" +image_sha_file="$remote_dir/.container-image.sha" + +mkdir -p "$downloads_dir" "$work_dir" "$output_dir" "$rootfs_dir" "$home_dir" + +build_args=() +run_proxy_env=() +if [[ -n "$proxy_url" ]]; then + for key in http_proxy https_proxy HTTP_PROXY HTTPS_PROXY; do + build_args+=(--build-arg "$key=$proxy_url") + run_proxy_env+=(-e "$key=$proxy_url") + done + build_args+=(--build-arg "no_proxy=$no_proxy") + build_args+=(--build-arg "NO_PROXY=$no_proxy") + run_proxy_env+=(-e "no_proxy=$no_proxy") + run_proxy_env+=(-e "NO_PROXY=$no_proxy") +fi + +dockerfile_sha="$(sha256sum "$dockerfile" | awk '{print $1}')" +current_image_sha="$(cat "$image_sha_file" 2>/dev/null || true)" +force_rebuild=0 +case "$rebuild_image" in + 1|true|TRUE|yes|YES|on|ON) force_rebuild=1 ;; +esac + +if [[ "$force_rebuild" == "0" ]] \ + && [[ "$current_image_sha" == "$dockerfile_sha" ]] \ + && "$runtime" image inspect "$image" >/dev/null 2>&1; then + echo "[INFO] 使用已有容器镜像: $image" +else + "$runtime" build \ + "${build_args[@]}" \ + --label "tuxwine.dockerfile-sha=$dockerfile_sha" \ + -t "$image" \ + -f "$dockerfile" \ + "$repo_dir" + printf '%s\n' "$dockerfile_sha" > "$image_sha_file" +fi + +uid="$(id -u)" +gid="$(id -g)" + +"$runtime" run --rm \ + --security-opt label=disable \ + --user "$uid:$gid" \ + --workdir /work/repo \ + -e HOME=/work/work/home \ + -e TUXWINE_INSIDE_CONTAINER=1 \ + -e BUILD_IN_CONTAINER=1 \ + -e TARGET_HOST="$target_host" \ + -e BUILD_HOST="$build_host" \ + -e PREFIX="$prefix" \ + -e PKG_CONFIG_LIBDIR="$pkg_config_libdir" \ + -e PKG_CONFIG_SYSROOT_DIR="$pkg_config_sysroot_dir" \ + -e MAKE_INSTALL_PROGRAM="$make_install_program" \ + -e DOWNLOAD_DIR=/work/downloads \ + -e WORK_DIR=/work/work \ + -e OUTPUT_DIR=/work/output \ + "${run_proxy_env[@]}" \ + -v "$repo_dir:/work/repo" \ + -v "$downloads_dir:/work/downloads" \ + -v "$work_dir:/work/work" \ + -v "$output_dir:/work/output" \ + -v "$rootfs_dir:$prefix_root" \ + "$image" \ + bash ./start-build.sh "$@" +REMOTE_SCRIPT +} + +maybe_delegate_to_container() { + container_truthy "${BUILD_IN_CONTAINER:-0}" || return 0 + + if [[ "${TUXWINE_INSIDE_CONTAINER:-0}" == "1" ]]; then + return 0 + fi + + container_require_local_tools + + local remote="${CONTAINER_REMOTE:-bazzite@192.168.0.100}" + local remote_dir="${CONTAINER_REMOTE_DIR:-/home/bazzite/Documents/rootfs-packages-build}" + local runtime="${CONTAINER_RUNTIME:-docker}" + local image="${CONTAINER_IMAGE:-tuxwine-rootfs-builder:ubuntu24}" + + info "BUILD_IN_CONTAINER=1,委托到 ${remote}:${remote_dir}" + container_remote_prepare "$remote" "$remote_dir" + container_sync_repo "$remote" "$remote_dir" + container_select_proxy "$remote" + container_remote_run "$remote" "$remote_dir" "$runtime" "$image" "$CONTAINER_SELECTED_PROXY" "$@" + exit $? +} diff --git a/main/main.sh b/main/main.sh index 3c8d7f5..4e8520e 100755 --- a/main/main.sh +++ b/main/main.sh @@ -12,7 +12,7 @@ set -euo pipefail MAIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$MAIN_DIR")" -for lib in common fetch unpack rebuild; do +for lib in common fetch unpack rebuild container; do if [[ -f "${PROJECT_ROOT}/lib/${lib}.sh" ]]; then source "${PROJECT_ROOT}/lib/${lib}.sh" else @@ -26,9 +26,15 @@ if [[ -f "${PROJECT_ROOT}/config.sh" ]]; then fi PACKAGES_DIR="${PROJECT_ROOT}/packages" -DOWNLOAD_DIR="${PROJECT_ROOT}/downloads" -WORK_DIR="${PROJECT_ROOT}/work" -OUTPUT_DIR="${PROJECT_ROOT}/output" +DOWNLOAD_DIR="${DOWNLOAD_DIR:-${PROJECT_ROOT}/downloads}" +WORK_DIR="${WORK_DIR:-${PROJECT_ROOT}/work}" +OUTPUT_DIR="${OUTPUT_DIR:-${PROJECT_ROOT}/output}" + +maybe_delegate_to_container "$@" + +if (( BASH_VERSINFO[0] < 4 )); then + error "当前 Bash 版本过低,需要 Bash 4+;macOS 可设置 BUILD_IN_CONTAINER=1 委托到远程 Linux 构建" +fi declare -A PKG_RECIPE_DIR declare -A BUILT_PKGS @@ -184,4 +190,4 @@ main() { info "所有任务完成。" } -main "$@" \ No newline at end of file +main "$@" diff --git a/md.txt b/md.txt index 8ec9131..b48f474 100644 --- a/md.txt +++ b/md.txt @@ -168,6 +168,26 @@ # CCACHE_DIR 指定 ccache 缓存目录,加速重复构建 # BUILD_IN_CONTAINER 标记是否需要在容器/chroot 中构建 +# ============================================================================= +# 9.1 远程 Docker 构建模式 +# ============================================================================= +# BUILD_IN_CONTAINER=1 时,主脚本会在本地解析 config.sh 后立即委托到远程 +# Linux Docker 环境执行,避免 macOS/BSD 工具链差异影响构建。 +# +# 可配置变量: +# CONTAINER_RUNTIME 容器运行时,默认 docker +# CONTAINER_REMOTE 远程 SSH 目标,默认 bazzite@192.168.0.100 +# CONTAINER_REMOTE_DIR 远程持久目录,默认 ~/Documents/rootfs-packages-build +# CONTAINER_IMAGE 构建镜像名,默认 tuxwine-rootfs-builder:ubuntu24 +# CONTAINER_PROXY_URL 网络代理地址,默认 http://192.168.0.102:8080 +# CONTAINER_PROXY_MODE auto/always/off,默认 auto +# CONTAINER_REBUILD_IMAGE 设置为 1 时强制重建容器镜像 +# +# 运行示例: +# BUILD_IN_CONTAINER=1 CONTAINER_SSH_PASSWORD=... ./start-build.sh zlib +# +# 注意:CONTAINER_SSH_PASSWORD 只从运行环境读取,不要写入 config.sh。 + # ============================================================================= # 10. 开发约定与注意事项 # =============================================================================