Ctrl K
ComunidadGuías…

scripts/package.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
set -euo pipefail

OUT_DIR="release"
TARGETS=()
ARCHIVE_FORMAT=""
FRONTEND_DIST_DIR="build/frontend/dist"
VERSION=""
USE_GIT_TAG="false"
RELEASE_MODE="false"
NODE_RELEASE_VERSION=""
NODE_LOCAL_DIR=""
NODE_LOCAL_DEFAULT_DIR="deploy/node"
NODE_REMOTE_URL="https://github.com/Ithildur/Ithiltir-node.git"
NODE_REPO_SLUG="Ithildur/Ithiltir-node"
BUILD_CHANNEL="release"
WITH_PVE="false"
NODE_ASSET_MANIFEST=(
  "node_linux_amd64_sha256=linux/node_linux_amd64"
  "node_linux_arm64_sha256=linux/node_linux_arm64"
  "node_macos_arm64_sha256=macos/node_macos_arm64"
  "node_windows_amd64_sha256=windows/node_windows_amd64.exe"
  "node_windows_arm64_sha256=windows/node_windows_arm64.exe"
  "runner_windows_amd64_sha256=windows/runner_windows_amd64.exe"
  "runner_windows_arm64_sha256=windows/runner_windows_arm64.exe"
)

usage() {
  cat <<'EOF'
Usage:
  scripts/package.sh [-o OUT_DIR] [-t TARGETS] [--version VERSION|--use-git-tag] [--node-version VERSION] [--node-local|--node-local-dir DIR] [--release] [-z|-zip|--zip|--tar-gz]

Options:
  --with-pve       Bundle the optional precompiled PVE cache helpers
  -o OUT_DIR        Output directory (default: release)
  -t TARGETS        Target os/arch list. Repeatable or comma-separated (default: linux/amd64)
  --version VERSION Build version to inject into dash (default: 0.0.0-dev)
  --use-git-tag     Use the single git tag pointing at HEAD as the build version
  --node-version VERSION
                    Ithiltir-node release version to bundle
                    (default: latest compatible remote tag; prerelease builds fall back to release when release is newer)
  --node-local      Copy node binaries from deploy/node instead of downloading release assets
  --node-local-dir DIR
                    Copy node binaries from local deploy directory or Ithiltir-node-* release binaries
  --release         Require --use-git-tag and a strict SemVer release tag:
                    MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
  -z|-zip|--zip     Write zip file
  --tar-gz          Write tar.gz file (recommended for linux targets)

Examples:
  scripts/package.sh
  scripts/package.sh -o release -t linux/amd64,linux/arm64
  scripts/package.sh -o release -t linux/amd64 -t windows/amd64
  scripts/package.sh -o release -t linux/amd64 -z
  scripts/package.sh -o release -t linux/amd64 --tar-gz
  scripts/package.sh -o release -t linux/amd64 -zip
  scripts/package.sh --version 1.2.3-alpha.1 --node-version 1.2.3-alpha.1 -o release -t linux/amd64 --tar-gz
  scripts/package.sh --version 1.2.3-alpha.1 --node-version 1.2.3-alpha.1 --node-local -o release -t linux/amd64 --tar-gz
  scripts/package.sh --use-git-tag --release -o release -t linux/amd64 --tar-gz
EOF
}

trim() {
  local s="$1"
  s="${s#"${s%%[![:space:]]*}"}"
  s="${s%"${s##*[![:space:]]}"}"
  printf '%s' "$s"
}

sha256_file() {
  local file="$1"
  local sum
  if command -v sha256sum >/dev/null 2>&1; then
    sum="$(sha256sum "$file" | awk '{print $1}')"
  elif command -v shasum >/dev/null 2>&1; then
    sum="$(shasum -a 256 "$file" | awk '{print $1}')"
  else
    echo "sha256sum or shasum is required to package node assets" >&2
    return 1
  fi
  if [[ ! "$sum" =~ ^[0-9a-fA-F]{64}$ ]]; then
    echo "invalid SHA-256 output for $file" >&2
    return 1
  fi
  printf '%s' "$sum" | tr '[:upper:]' '[:lower:]'
}

version_tool() {
  if ! command -v go >/dev/null 2>&1; then
    echo "go is required to validate release versions" >&2
    return 1
  fi
  go -C "$repo_root" run ./cmd/releasever "$@"
}

get_git_tag() {
  if [[ "${GITHUB_REF_TYPE:-}" == "tag" && -n "${GITHUB_REF_NAME:-}" ]]; then
    printf '%s\n' "$GITHUB_REF_NAME"
    return
  fi

  local tags
  tags="$(git -C "$repo_root" tag --points-at HEAD 2>/dev/null | sed '/^$/d' || true)"
  local count
  count="$(printf '%s\n' "$tags" | sed '/^$/d' | wc -l | tr -d ' ')"
  if [[ "$count" != "1" ]]; then
    echo "current commit must have exactly one git tag" >&2
    if [[ -n "$tags" ]]; then
      printf '%s\n' "$tags" >&2
    fi
    return 1
  fi

  printf '%s\n' "$tags"
}

resolve_version() {
  if [[ "$USE_GIT_TAG" == "true" ]]; then
    if ! VERSION="$(get_git_tag)"; then
      exit 1
    fi
  fi

  if [[ "$RELEASE_MODE" == "true" && "$USE_GIT_TAG" != "true" ]]; then
    echo "release mode requires --use-git-tag" >&2
    exit 2
  fi

  if [[ -z "$VERSION" ]]; then
    VERSION="0.0.0-dev"
  fi
  VERSION="$(trim "$VERSION")"

  if BUILD_CHANNEL="$(version_tool channel "$VERSION" 2>/dev/null)"; then
    return
  fi

  BUILD_CHANNEL="release"
  if [[ "$RELEASE_MODE" == "true" ]]; then
    echo "release version must be strict SemVer without a v prefix: $VERSION" >&2
    exit 2
  fi

  echo "version must be strict SemVer without a v prefix: $VERSION" >&2
  exit 2
}

latest_remote_tag() {
  local remote_url="$1"
  local channel="$2"
  local refs
  local tags latest

  if refs="$(git ls-remote --tags --refs --sort='v:refname' "$remote_url" 2>/dev/null)"; then
    :
  else
    refs="$(git ls-remote --tags --refs "$remote_url")"
  fi

  tags="$(printf '%s\n' "$refs" | awk '{print $2}' | sed 's#^refs/tags/##' | sed '/^$/d')"
  latest="$(version_tool latest "$channel" <<<"$tags" 2>/dev/null)" || return 1
  printf '%s\n' "$latest"
}

resolve_node_version() {
  if [[ -z "$NODE_RELEASE_VERSION" && -n "${ITHILTIR_NODE_VERSION:-}" ]]; then
    NODE_RELEASE_VERSION="$ITHILTIR_NODE_VERSION"
  fi

  if [[ -n "$NODE_LOCAL_DIR" && -z "$NODE_RELEASE_VERSION" ]]; then
    echo "local node packaging requires --node-version or ITHILTIR_NODE_VERSION" >&2
    exit 2
  fi

  if [[ -z "$NODE_RELEASE_VERSION" ]]; then
    if ! command -v git >/dev/null 2>&1; then
      echo "git is required to resolve latest Ithiltir-node release tag" >&2
      exit 1
    fi
    if ! NODE_RELEASE_VERSION="$(latest_remote_tag "$NODE_REMOTE_URL" "$BUILD_CHANNEL")"; then
      echo "no valid compatible Ithiltir-node tags found on $NODE_REMOTE_URL for $BUILD_CHANNEL build" >&2
      exit 1
    fi
  fi

  NODE_RELEASE_VERSION="$(trim "$NODE_RELEASE_VERSION")"
  if ! version_tool validate "$NODE_RELEASE_VERSION" >/dev/null 2>&1; then
    echo "node version must be strict SemVer without a v prefix: $NODE_RELEASE_VERSION" >&2
    exit 2
  fi

  if [[ "$RELEASE_MODE" == "true" ]]; then
    local node_channel
    node_channel="$(version_tool channel "$NODE_RELEASE_VERSION")"
    if [[ "$BUILD_CHANNEL" == "release" && "$node_channel" != "release" ]]; then
      echo "node version channel ($node_channel) must match dash version channel ($BUILD_CHANNEL)" >&2
      exit 2
    fi
  fi
}

set_node_permissions() {
  local deploy_dir="$1"

  chmod 755 "$deploy_dir/linux/node_linux_amd64" \
    "$deploy_dir/linux/node_linux_arm64" \
    "$deploy_dir/macos/node_macos_arm64"
  chmod 644 "$deploy_dir/windows/node_windows_amd64.exe" \
    "$deploy_dir/windows/node_windows_arm64.exe" \
    "$deploy_dir/windows/runner_windows_amd64.exe" \
    "$deploy_dir/windows/runner_windows_arm64.exe"
}

download_file() {
  local url="$1"
  local output="$2"

  if command -v curl >/dev/null 2>&1; then
    curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fL --retry 3 --connect-timeout 10 --max-time 600 -o "$output" "$url"
    return
  fi
  if command -v wget >/dev/null 2>&1; then
    wget --https-only --secure-protocol=TLSv1_2 -O "$output" "$url"
    return
  fi
  echo "curl or wget is required to download Ithiltir-node release assets" >&2
  exit 1
}

download_node_asset() {
  local asset="$1"
  local output="$2"
  local url

  url="https://github.com/${NODE_REPO_SLUG}/releases/download/${NODE_RELEASE_VERSION}/${asset}"
  echo "downloading node asset: $url"
  mkdir -p "$(dirname "$output")"
  download_file "$url" "$output"
}

prepare_remote_node_deploy() {
  local deploy_dir="$1"

  rm -rf "$deploy_dir"
  mkdir -p "$deploy_dir/linux" "$deploy_dir/macos" "$deploy_dir/windows"

  download_node_asset "Ithiltir-node-linux-amd64" "$deploy_dir/linux/node_linux_amd64"
  download_node_asset "Ithiltir-node-linux-arm64" "$deploy_dir/linux/node_linux_arm64"
  download_node_asset "Ithiltir-node-macos-arm64" "$deploy_dir/macos/node_macos_arm64"
  download_node_asset "Ithiltir-node-windows-amd64.exe" "$deploy_dir/windows/node_windows_amd64.exe"
  download_node_asset "Ithiltir-node-windows-arm64.exe" "$deploy_dir/windows/node_windows_arm64.exe"
  download_node_asset "Ithiltir-runner-windows-amd64.exe" "$deploy_dir/windows/runner_windows_amd64.exe"
  download_node_asset "Ithiltir-runner-windows-arm64.exe" "$deploy_dir/windows/runner_windows_arm64.exe"

  set_node_permissions "$deploy_dir"
}

copy_local_node_asset() {
  local output="$1"
  shift

  local source
  for source in "$@"; do
    if [[ -f "$source" ]]; then
      mkdir -p "$(dirname "$output")"
      cp "$source" "$output"
      return
    fi
  done

  echo "local node asset not found. tried:" >&2
  printf '  %s\n' "$@" >&2
  exit 1
}

prepare_local_node_deploy() {
  local source_dir="$1"
  local deploy_dir="$2"

  if [[ ! -d "$source_dir" ]]; then
    echo "local node deploy directory not found: $source_dir" >&2
    exit 1
  fi

  rm -rf "$deploy_dir"
  mkdir -p "$deploy_dir/linux" "$deploy_dir/macos" "$deploy_dir/windows"

  copy_local_node_asset "$deploy_dir/linux/node_linux_amd64" \
    "$source_dir/linux/node_linux_amd64" \
    "$source_dir/linux/Ithiltir-node-linux-amd64" \
    "$source_dir/Ithiltir-node-linux-amd64"
  copy_local_node_asset "$deploy_dir/linux/node_linux_arm64" \
    "$source_dir/linux/node_linux_arm64" \
    "$source_dir/linux/Ithiltir-node-linux-arm64" \
    "$source_dir/Ithiltir-node-linux-arm64"
  copy_local_node_asset "$deploy_dir/macos/node_macos_arm64" \
    "$source_dir/macos/node_macos_arm64" \
    "$source_dir/macos/Ithiltir-node-macos-arm64" \
    "$source_dir/Ithiltir-node-macos-arm64"
  copy_local_node_asset "$deploy_dir/windows/node_windows_amd64.exe" \
    "$source_dir/windows/node_windows_amd64.exe" \
    "$source_dir/windows/Ithiltir-node-windows-amd64.exe" \
    "$source_dir/Ithiltir-node-windows-amd64.exe"
  copy_local_node_asset "$deploy_dir/windows/node_windows_arm64.exe" \
    "$source_dir/windows/node_windows_arm64.exe" \
    "$source_dir/windows/Ithiltir-node-windows-arm64.exe" \
    "$source_dir/Ithiltir-node-windows-arm64.exe"
  copy_local_node_asset "$deploy_dir/windows/runner_windows_amd64.exe" \
    "$source_dir/windows/runner_windows_amd64.exe" \
    "$source_dir/windows/Ithiltir-runner-windows-amd64.exe" \
    "$source_dir/Ithiltir-runner-windows-amd64.exe"
  copy_local_node_asset "$deploy_dir/windows/runner_windows_arm64.exe" \
    "$source_dir/windows/runner_windows_arm64.exe" \
    "$source_dir/windows/Ithiltir-runner-windows-arm64.exe" \
    "$source_dir/Ithiltir-runner-windows-arm64.exe"

  set_node_permissions "$deploy_dir"
}

set_archive_format() {
  local format="$1"

  if [[ -n "$ARCHIVE_FORMAT" && "$ARCHIVE_FORMAT" != "$format" ]]; then
    echo "archive format already set to $ARCHIVE_FORMAT" >&2
    exit 2
  fi

  ARCHIVE_FORMAT="$format"
}

detect_zip_package_manager() {
  if [[ "$(uname -s)" == "Darwin" ]]; then
    if command -v brew >/dev/null 2>&1; then
      echo "brew"
      return 0
    fi
  fi

  if [[ -r /etc/os-release ]]; then
    # shellcheck disable=SC1091
    . /etc/os-release

    case "${ID:-}" in
      ubuntu|debian)
        echo "apt-get"
        return 0
        ;;
      fedora)
        echo "dnf"
        return 0
        ;;
      rhel|centos|rocky|almalinux|ol)
        if command -v dnf >/dev/null 2>&1; then
          echo "dnf"
        else
          echo "yum"
        fi
        return 0
        ;;
      arch|manjaro)
        echo "pacman"
        return 0
        ;;
      alpine)
        echo "apk"
        return 0
        ;;
      opensuse*|sles)
        echo "zypper"
        return 0
        ;;
    esac

    for like in ${ID_LIKE:-}; do
      case "$like" in
        debian)
          echo "apt-get"
          return 0
          ;;
        fedora|rhel)
          if command -v dnf >/dev/null 2>&1; then
            echo "dnf"
          else
            echo "yum"
          fi
          return 0
          ;;
        arch)
          echo "pacman"
          return 0
          ;;
        alpine)
          echo "apk"
          return 0
          ;;
        suse)
          echo "zypper"
          return 0
          ;;
      esac
    done
  fi

  for pm in apt-get dnf yum pacman apk zypper brew; do
    if command -v "$pm" >/dev/null 2>&1; then
      echo "$pm"
      return 0
    fi
  done

  return 1
}

run_with_privileges() {
  if [[ "$(id -u)" -eq 0 ]]; then
    "$@"
    return
  fi

  if command -v sudo >/dev/null 2>&1; then
    sudo "$@"
    return
  fi

  echo "need root or sudo to install zip automatically" >&2
  return 1
}

ensure_zip() {
  if command -v zip >/dev/null 2>&1; then
    return 0
  fi

  local pm
  if ! pm="$(detect_zip_package_manager)"; then
    echo "zip not found and no supported package manager detected" >&2
    return 1
  fi

  echo "zip not found. installing via $pm..." >&2
  case "$pm" in
    apt-get)
      run_with_privileges apt-get update
      run_with_privileges apt-get install -y zip
      ;;
    dnf)
      run_with_privileges dnf install -y zip
      ;;
    yum)
      run_with_privileges yum install -y zip
      ;;
    pacman)
      run_with_privileges pacman -Sy --noconfirm zip
      ;;
    apk)
      run_with_privileges apk add zip
      ;;
    zypper)
      run_with_privileges zypper --non-interactive install zip
      ;;
    brew)
      brew install zip
      ;;
    *)
      echo "unsupported package manager: $pm" >&2
      return 1
      ;;
  esac

  if ! command -v zip >/dev/null 2>&1; then
    echo "zip installation finished but zip is still unavailable in PATH" >&2
    return 1
  fi
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --with-pve)
      WITH_PVE="true"
      shift
      ;;
    -o)
      OUT_DIR="${2:-}"
      [[ -n "$OUT_DIR" ]] || { echo "missing value for -o" >&2; usage; exit 2; }
      shift 2
      ;;
    -t)
      target_value="${2:-}"
      [[ -n "$target_value" ]] || { echo "missing value for -t" >&2; usage; exit 2; }
      TARGETS+=("$target_value")
      shift 2
      ;;
    --version)
      VERSION="${2:-}"
      [[ -n "$VERSION" ]] || { echo "missing value for --version" >&2; usage; exit 2; }
      shift 2
      ;;
    --use-git-tag)
      USE_GIT_TAG="true"
      shift
      ;;
    --node-version)
      NODE_RELEASE_VERSION="${2:-}"
      [[ -n "$NODE_RELEASE_VERSION" ]] || { echo "missing value for --node-version" >&2; usage; exit 2; }
      shift 2
      ;;
    --node-local)
      [[ -n "$NODE_LOCAL_DIR" ]] || NODE_LOCAL_DIR="$NODE_LOCAL_DEFAULT_DIR"
      shift
      ;;
    --node-local-dir)
      NODE_LOCAL_DIR="${2:-}"
      [[ -n "$NODE_LOCAL_DIR" ]] || { echo "missing value for --node-local-dir" >&2; usage; exit 2; }
      shift 2
      ;;
    --release)
      RELEASE_MODE="true"
      shift
      ;;
    -z|-zip|--zip)
      set_archive_format "zip"
      shift
      ;;
    --tar-gz)
      set_archive_format "tar.gz"
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "unknown option: $1" >&2
      usage
      exit 2
      ;;
  esac
done

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ -n "$NODE_LOCAL_DIR" && "$NODE_LOCAL_DIR" != /* ]]; then
  NODE_LOCAL_DIR="$repo_root/$NODE_LOCAL_DIR"
fi
if ! command -v go >/dev/null 2>&1; then
  echo "go is required to build dash" >&2
  exit 1
fi
resolve_version
resolve_node_version
ldflags="-s -w -X dash/internal/version.Current=${VERSION} -X dash/internal/version.BundledNode=${NODE_RELEASE_VERSION}"
echo "dash version: $VERSION"
echo "dash channel: $BUILD_CHANNEL"
echo "node version: $NODE_RELEASE_VERSION"
if [[ -n "$NODE_LOCAL_DIR" ]]; then
  echo "node source: $NODE_LOCAL_DIR"
fi

if [[ "$OUT_DIR" == /* ]]; then
  out_root="$OUT_DIR"
else
  out_root="$repo_root/$OUT_DIR"
fi

mkdir -p "$out_root"

dist_root="$repo_root/build"
node_deploy_dir="$dist_root/node-deploy"

if [[ ${#TARGETS[@]} -eq 0 ]]; then
  TARGETS=("linux/amd64")
fi

target_list=()
for raw_targets in "${TARGETS[@]}"; do
  IFS=',' read -r -a split_targets <<<"$raw_targets"
  for t in "${split_targets[@]}"; do
    t="${t//[[:space:]]/}"
    [[ -n "$t" ]] || continue
    target_list+=("$t")
  done
done

if [[ ${#target_list[@]} -eq 0 ]]; then
  echo "no valid targets provided" >&2
  exit 2
fi

for t in "${target_list[@]}"; do
  os="${t%%/*}"
  arch="${t#*/}"
  if [[ -z "$os" || -z "$arch" || "$os" == "$arch" ]]; then
    echo "invalid target: $t (expected os/arch)" >&2
    exit 2
  fi
done

if [[ -n "$NODE_LOCAL_DIR" ]]; then
  prepare_local_node_deploy "$NODE_LOCAL_DIR" "$node_deploy_dir"
else
  prepare_remote_node_deploy "$node_deploy_dir"
fi

if [[ "$WITH_PVE" == "true" ]]; then
  for pve_arch in amd64 arm64; do
    pve_file="pve_cache_linux_${pve_arch}"
    if [[ -n "$NODE_LOCAL_DIR" ]]; then
      copy_local_node_asset "$node_deploy_dir/linux/$pve_file" \
        "$NODE_LOCAL_DIR/linux/$pve_file" \
        "$NODE_LOCAL_DIR/Ithiltir-pve-cache-linux-${pve_arch}"
    else
      download_node_asset "Ithiltir-pve-cache-linux-${pve_arch}" "$node_deploy_dir/linux/$pve_file"
    fi
    chmod 755 "$node_deploy_dir/linux/$pve_file"
  done
  {
    printf 'format_version=1\nnode_version=%s\n' "$NODE_RELEASE_VERSION"
    for pve_arch in amd64 arm64; do
      printf '%s_sha256=%s\n' "$pve_arch" "$(sha256_file "$node_deploy_dir/linux/pve_cache_linux_${pve_arch}")"
    done
  } >"$node_deploy_dir/linux/pve-cache.env"
fi

node_asset_sums=()
for spec in "${NODE_ASSET_MANIFEST[@]}"; do
  field="${spec%%=*}"
  asset="${spec#*=}"
  node_asset_sums+=("${field}=$(sha256_file "$node_deploy_dir/$asset")")
done

binary_path_for_target() {
  local os="$1"
  local arch="$2"
  local os_dir="$os"
  local bin_name="dash_${arch}"

  if [[ "$os" == "darwin" ]]; then
    os_dir="macos"
  fi
  if [[ "$os" == "windows" ]]; then
    bin_name="${bin_name}.exe"
  fi

  echo "$dist_root/$os_dir/$bin_name"
}

build_dash_binary() {
  local os="$1"
  local arch="$2"
  local output
  output="$(binary_path_for_target "$os" "$arch")"

  mkdir -p "$(dirname "$output")"
  echo "building dash for ${os}/${arch}: $output"
  (
    cd "$repo_root"
    env GOOS="$os" GOARCH="$arch" CGO_ENABLED=0 go build -trimpath -ldflags "$ldflags" -o "$output" ./cmd/dash
  )

}

for t in "${target_list[@]}"; do
  os="${t%%/*}"
  arch="${t#*/}"
  if [[ -z "$os" || -z "$arch" || "$os" == "$arch" ]]; then
    echo "invalid target: $t (expected os/arch)" >&2
    exit 2
  fi
  build_dash_binary "$os" "$arch"
done

bash "$repo_root/scripts/build_frontend.sh" -o "$FRONTEND_DIST_DIR" --version "$VERSION"

frontend_dist_dir="$repo_root/$FRONTEND_DIST_DIR"
if [[ ! -d "$frontend_dist_dir" ]]; then
  echo "frontend build output not found: $frontend_dist_dir" >&2
  exit 1
fi

if [[ "$ARCHIVE_FORMAT" == "zip" ]]; then
  ensure_zip
fi

if [[ "$ARCHIVE_FORMAT" == "tar.gz" ]] && ! command -v tar >/dev/null 2>&1; then
  echo "tar is required to create tar.gz archives" >&2
  exit 1
fi

for t in "${target_list[@]}"; do
  os="${t%%/*}"
  arch="${t#*/}"
  if [[ -z "$os" || -z "$arch" || "$os" == "$arch" ]]; then
    echo "invalid target: $t (expected os/arch)" >&2
    exit 2
  fi

  build_root="$out_root/build"
  pkg_root="$build_root/Ithiltir-dash"
  rm -rf "$pkg_root"
  mkdir -p "$build_root"
  mkdir -p "$pkg_root/bin" "$pkg_root/configs" "$pkg_root/logs"

  exe_name="dash"
  if [[ "$os" == "windows" ]]; then
    exe_name="dash.exe"
  fi

  source_bin="$(binary_path_for_target "$os" "$arch")"
  if [[ ! -f "$source_bin" ]]; then
    echo "dash build output not found: $source_bin" >&2
    exit 1
  fi

  cp "$source_bin" "$pkg_root/bin/$exe_name"
  {
    printf 'format_version=1\ndash_version=%s\nnode_version=%s\ntarget_os=%s\ntarget_arch=%s\n' \
      "$VERSION" "$NODE_RELEASE_VERSION" "$os" "$arch"
    printf '%s\n' "${node_asset_sums[@]}"
  } >"$pkg_root/release.env"

  cp "$repo_root/configs/config.example.yaml" "$pkg_root/configs/config.example.yaml"
  cp -R "$frontend_dist_dir" "$pkg_root/dist"
  cp -R "$node_deploy_dir" "$pkg_root/deploy"
  cp "$repo_root/install_dash_linux.sh" "$repo_root/update_dash_linux.sh" "$pkg_root/"

  if [[ "$os" != "windows" ]]; then
    chmod 755 "$pkg_root/bin/$exe_name"
    find "$pkg_root" -type f -name '*.sh' -exec chmod 755 {} +
  fi

  case "$ARCHIVE_FORMAT" in
    zip)
      zip_path="$out_root/Ithiltir_dash_${os}_${arch}.zip"
      rm -f "$zip_path"
      (cd "$build_root" && zip -r "$zip_path" "Ithiltir-dash" >/dev/null)
      ;;
    tar.gz)
      tar_gz_path="$out_root/Ithiltir_dash_${os}_${arch}.tar.gz"
      rm -f "$tar_gz_path"
      (cd "$build_root" && tar -czf "$tar_gz_path" "Ithiltir-dash")
      ;;
  esac
done

echo "done. output: $out_root"