199 lines
7.0 KiB
Bash
199 lines
7.0 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
|
|
|
||
|
|
SOURCE_DIR="$REPO_ROOT/out/linux/bin"
|
||
|
|
CONFIG_FILE=""
|
||
|
|
OUTPUT_DIR="$REPO_ROOT/dist/UpdateClient-linux"
|
||
|
|
ARCHIVE_FILE="$REPO_ROOT/dist/UpdateClient-linux.tar.gz"
|
||
|
|
SKIP_MANIFEST_CHECK=0
|
||
|
|
|
||
|
|
usage() {
|
||
|
|
cat <<'EOF'
|
||
|
|
Usage: package-client.sh --config-file FILE [options]
|
||
|
|
|
||
|
|
Options:
|
||
|
|
--source-dir DIR Release/install root to package. Default: ./out/linux/bin
|
||
|
|
--config-file FILE app_config.json used by this client package. Required.
|
||
|
|
--output-dir DIR Output directory. Default: ./dist/UpdateClient-linux
|
||
|
|
--archive FILE Output tar.gz. Default: ./dist/UpdateClient-linux.tar.gz
|
||
|
|
--skip-manifest-check Skip current-version manifest cache check.
|
||
|
|
-h, --help Show this help.
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
normalize_relative_path() {
|
||
|
|
local value="${1:-}"
|
||
|
|
value="${value//\\//}"
|
||
|
|
value="${value#/}"
|
||
|
|
value="${value%/}"
|
||
|
|
printf '%s' "$value"
|
||
|
|
}
|
||
|
|
|
||
|
|
join_relative_path() {
|
||
|
|
local base
|
||
|
|
local child
|
||
|
|
base="$(normalize_relative_path "${1:-}")"
|
||
|
|
child="$(normalize_relative_path "${2:-}")"
|
||
|
|
if [[ -z "$base" ]]; then printf '%s' "$child"; return; fi
|
||
|
|
if [[ -z "$child" ]]; then printf '%s' "$base"; return; fi
|
||
|
|
printf '%s/%s' "$base" "$child"
|
||
|
|
}
|
||
|
|
|
||
|
|
json_value() {
|
||
|
|
python3 - "$CONFIG_FILE" "$1" <<'PY'
|
||
|
|
import json
|
||
|
|
import sys
|
||
|
|
with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
value = data.get(sys.argv[2], "")
|
||
|
|
print("" if value is None else value)
|
||
|
|
PY
|
||
|
|
}
|
||
|
|
|
||
|
|
relative_to_source() {
|
||
|
|
local full="$1"
|
||
|
|
local fallback="$2"
|
||
|
|
python3 - "$SOURCE_DIR" "$full" "$fallback" <<'PY'
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
source = os.path.realpath(sys.argv[1])
|
||
|
|
full = os.path.realpath(sys.argv[2])
|
||
|
|
fallback = sys.argv[3]
|
||
|
|
try:
|
||
|
|
rel = os.path.relpath(full, source)
|
||
|
|
except ValueError:
|
||
|
|
rel = fallback
|
||
|
|
if rel.startswith(".."):
|
||
|
|
rel = fallback
|
||
|
|
print(rel.replace(os.sep, "/").strip("/"))
|
||
|
|
PY
|
||
|
|
}
|
||
|
|
|
||
|
|
while [[ $# -gt 0 ]]; do
|
||
|
|
case "$1" in
|
||
|
|
--source-dir) SOURCE_DIR="$2"; shift 2 ;;
|
||
|
|
--config-file) CONFIG_FILE="$2"; shift 2 ;;
|
||
|
|
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
|
||
|
|
--archive|--tar-file|--zip-file) ARCHIVE_FILE="$2"; shift 2 ;;
|
||
|
|
--skip-manifest-check) SKIP_MANIFEST_CHECK=1; shift ;;
|
||
|
|
-h|--help) usage; exit 0 ;;
|
||
|
|
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
if [[ -z "$CONFIG_FILE" ]]; then
|
||
|
|
echo "--config-file is required." >&2
|
||
|
|
usage >&2
|
||
|
|
exit 2
|
||
|
|
fi
|
||
|
|
|
||
|
|
SOURCE_DIR="$(realpath "$SOURCE_DIR")"
|
||
|
|
CONFIG_FILE="$(realpath "$CONFIG_FILE")"
|
||
|
|
OUTPUT_DIR="$(realpath -m "$OUTPUT_DIR")"
|
||
|
|
ARCHIVE_FILE="$(realpath -m "$ARCHIVE_FILE")"
|
||
|
|
|
||
|
|
for field in app_id channel api_base_url current_version client_token launch_token license_key main_executable launcher_executable updater_executable bootstrap_executable; do
|
||
|
|
if [[ -z "$(json_value "$field")" ]]; then
|
||
|
|
echo "Config file is missing required field: $field" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
CONFIG_RELATIVE_PATH="$(relative_to_source "$CONFIG_FILE" "config/app_config.json")"
|
||
|
|
CONFIG_RELATIVE_PARENT="$(dirname "$CONFIG_RELATIVE_PATH")"
|
||
|
|
[[ "$CONFIG_RELATIVE_PARENT" == "." ]] && CONFIG_RELATIVE_PARENT=""
|
||
|
|
RUNTIME_DIR_RELATIVE="$(normalize_relative_path "$(dirname "$CONFIG_RELATIVE_PARENT")")"
|
||
|
|
[[ "$RUNTIME_DIR_RELATIVE" == "." ]] && RUNTIME_DIR_RELATIVE=""
|
||
|
|
|
||
|
|
MAIN_EXECUTABLE="$(normalize_relative_path "$(json_value main_executable)")"
|
||
|
|
LAUNCHER_EXECUTABLE="$(normalize_relative_path "$(json_value launcher_executable)")"
|
||
|
|
UPDATER_EXECUTABLE="$(normalize_relative_path "$(json_value updater_executable)")"
|
||
|
|
BOOTSTRAP_EXECUTABLE="$(normalize_relative_path "$(json_value bootstrap_executable)")"
|
||
|
|
CURRENT_VERSION="$(json_value current_version)"
|
||
|
|
|
||
|
|
for required in \
|
||
|
|
"$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$MAIN_EXECUTABLE")" \
|
||
|
|
"$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$LAUNCHER_EXECUTABLE")" \
|
||
|
|
"$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$UPDATER_EXECUTABLE")" \
|
||
|
|
"$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$BOOTSTRAP_EXECUTABLE")"; do
|
||
|
|
if [[ ! -f "$SOURCE_DIR/$required" ]]; then
|
||
|
|
echo "Source directory is missing required file: $required" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
|
||
|
|
DEBUG_ARTIFACT="$(find "$SOURCE_DIR" -type f \( -name '*.pdb' -o -name '*.ilk' -o -name '*d.dll' \) -print -quit)"
|
||
|
|
if [[ -n "$DEBUG_ARTIFACT" ]]; then
|
||
|
|
echo "Source directory contains Debug artifacts. Use a clean Release root directory." >&2
|
||
|
|
echo "Example: $DEBUG_ARTIFACT" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
EXPECTED_MAIN_PATH="$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$MAIN_EXECUTABLE")"
|
||
|
|
MAIN_LEAF="$(basename "$MAIN_EXECUTABLE")"
|
||
|
|
DUPLICATE_MAIN="$({ find "$SOURCE_DIR" -type f -name "$MAIN_LEAF" | while read -r item; do
|
||
|
|
rel="$(python3 - "$SOURCE_DIR" "$item" <<'PY'
|
||
|
|
import os, sys
|
||
|
|
print(os.path.relpath(os.path.realpath(sys.argv[2]), os.path.realpath(sys.argv[1])).replace(os.sep, "/"))
|
||
|
|
PY
|
||
|
|
)"
|
||
|
|
[[ "$rel" != "$EXPECTED_MAIN_PATH" ]] && { echo "$item"; break; }
|
||
|
|
done; } || true)"
|
||
|
|
if [[ -n "$DUPLICATE_MAIN" ]]; then
|
||
|
|
echo "Source directory contains a duplicate main executable outside $EXPECTED_MAIN_PATH: $DUPLICATE_MAIN" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
MANIFEST_NAME="manifest_${CURRENT_VERSION}.json"
|
||
|
|
SOURCE_MANIFEST="$SOURCE_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "update/manifest_cache/$MANIFEST_NAME")"
|
||
|
|
if [[ ! -f "$SOURCE_MANIFEST" && -f "$SOURCE_DIR/update/manifest_cache/$MANIFEST_NAME" ]]; then
|
||
|
|
SOURCE_MANIFEST="$SOURCE_DIR/update/manifest_cache/$MANIFEST_NAME"
|
||
|
|
fi
|
||
|
|
if [[ "$SKIP_MANIFEST_CHECK" -eq 0 && ! -f "$SOURCE_MANIFEST" ]]; then
|
||
|
|
echo "Missing signed Manifest cache for current version: $SOURCE_MANIFEST. Complete online update/verification for this version before packaging." >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
rm -rf "$OUTPUT_DIR"
|
||
|
|
mkdir -p "$OUTPUT_DIR"
|
||
|
|
shopt -s dotglob nullglob
|
||
|
|
for item in "$SOURCE_DIR"/*; do
|
||
|
|
base="$(basename "$item")"
|
||
|
|
case "$base" in
|
||
|
|
update|update_temp) ;;
|
||
|
|
*) cp -a "$item" "$OUTPUT_DIR/" ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
shopt -u dotglob nullglob
|
||
|
|
|
||
|
|
rm -rf "$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "update")"
|
||
|
|
rm -rf "$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "update_temp")"
|
||
|
|
|
||
|
|
OUTPUT_CONFIG_PATH="$OUTPUT_DIR/$CONFIG_RELATIVE_PATH"
|
||
|
|
mkdir -p "$(dirname "$OUTPUT_CONFIG_PATH")"
|
||
|
|
cp "$CONFIG_FILE" "$OUTPUT_CONFIG_PATH"
|
||
|
|
rm -f "$(dirname "$OUTPUT_CONFIG_PATH")/client_identity.dat" \
|
||
|
|
"$(dirname "$OUTPUT_CONFIG_PATH")/local_state.json" \
|
||
|
|
"$(dirname "$OUTPUT_CONFIG_PATH")/version_policy.dat"
|
||
|
|
|
||
|
|
if [[ -f "$SOURCE_MANIFEST" ]]; then
|
||
|
|
MANIFEST_DIR="$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "update/manifest_cache")"
|
||
|
|
mkdir -p "$MANIFEST_DIR"
|
||
|
|
cp "$SOURCE_MANIFEST" "$MANIFEST_DIR/$MANIFEST_NAME"
|
||
|
|
fi
|
||
|
|
|
||
|
|
chmod +x "$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$LAUNCHER_EXECUTABLE")" \
|
||
|
|
"$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$UPDATER_EXECUTABLE")" \
|
||
|
|
"$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$BOOTSTRAP_EXECUTABLE")" \
|
||
|
|
"$OUTPUT_DIR/$(join_relative_path "$RUNTIME_DIR_RELATIVE" "$MAIN_EXECUTABLE")" 2>/dev/null || true
|
||
|
|
|
||
|
|
mkdir -p "$(dirname "$ARCHIVE_FILE")"
|
||
|
|
rm -f "$ARCHIVE_FILE"
|
||
|
|
tar -C "$OUTPUT_DIR" -czf "$ARCHIVE_FILE" .
|
||
|
|
|
||
|
|
echo "Package directory: $OUTPUT_DIR"
|
||
|
|
echo "Archive file: $ARCHIVE_FILE"
|