scripts/package.sh
Rama main · réplica de lectura del repositorio original.
#!/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"