#!/usr/bin/env bash set -euo pipefail repo_root="$(cd "$(dirname "$0")/.." && pwd)" push_mode=0 strict_image_check=0 positional=() while [[ $# -gt 0 ]]; do case "$1" in --push) push_mode=1 shift ;; --strict-images) strict_image_check=1 shift ;; -h|--help) # handled below via usage positional+=("$1") shift ;; *) positional+=("$1") shift ;; esac done if [[ ${#positional[@]} -gt 2 ]]; then echo "ERROR: too many arguments" exit 2 fi output_dir="${positional[0]:-$repo_root/dist}" #zip_name="${positional[1]:-zima-appstore-$(date +%Y%m%d-%H%M%S).zip}" zip_name="${positional[1]:-phirna-appstore.zip}" usage() { cat </dev/null 2>&1; then echo "ERROR: 'zip' command not found" exit 1 fi if [[ "${CI:-}" == "true" || "${GITEA_ACTIONS:-}" == "true" ]]; then strict_image_check=1 fi required_root_files=( "category-list.json" "recommend-list.json" "featured-apps.json" ) for file in "${required_root_files[@]}"; do if [[ ! -f "$repo_root/$file" ]]; then echo "ERROR: missing required file: $file" exit 1 fi done if [[ ! -d "$repo_root/Apps" ]]; then echo "ERROR: missing required directory: Apps" exit 1 fi extract_images_from_compose() { local compose_file="$1" awk ' /^[[:space:]]*image:[[:space:]]*/ { line=$0 sub(/^[[:space:]]*image:[[:space:]]*/, "", line) sub(/[[:space:]]+#.*/, "", line) gsub(/^["'"'"']|["'"'"']$/, "", line) if (line != "") print line } ' "$compose_file" } verify_images_online() { local -a images=() local -a missing=() local compose_file app_id while IFS= read -r compose_file; do app_id="$(basename "$(dirname "$compose_file")")" if [[ "$app_id" == "_template" ]]; then continue fi while IFS= read -r image_ref; do [[ -z "$image_ref" ]] && continue images+=("$image_ref") done < <(extract_images_from_compose "$compose_file") done < <(find "$repo_root/Apps" -type f -name 'docker-compose.yaml' | sort) if [[ ${#images[@]} -eq 0 ]]; then echo "WARN: no images found to verify" return 0 fi mapfile -t images < <(printf '%s\n' "${images[@]}" | sort -u) if ! command -v docker >/dev/null 2>&1; then echo "WARN: docker is not installed; skipping online image verification" if [[ "$strict_image_check" -eq 1 ]]; then echo "ERROR: strict image verification is enabled (CI/Gitea or --strict-images)" return 1 fi return 0 fi echo "Verifying ${#images[@]} image(s) online..." for image_ref in "${images[@]}"; do if docker manifest inspect "$image_ref" >/dev/null 2>&1; then echo "OK: $image_ref" else echo "WARN: image not found or inaccessible: $image_ref" missing+=("$image_ref") fi done if [[ ${#missing[@]} -gt 0 ]]; then echo echo "Image verification warnings:" for image_ref in "${missing[@]}"; do echo " - $image_ref" done if [[ "$strict_image_check" -eq 1 ]]; then echo echo "ERROR: strict image verification is enabled and one or more images are missing." return 1 fi fi } if ! verify_images_online; then exit 1 fi is_git_repo=0 if git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then is_git_repo=1 fi tmp_dir="$(mktemp -d)" staging_dir="$tmp_dir/appstore" cleanup() { rm -rf "$tmp_dir" } trap cleanup EXIT mkdir -p "$staging_dir/Apps" copy_file() { local rel="$1" mkdir -p "$staging_dir/$(dirname "$rel")" cp "$repo_root/$rel" "$staging_dir/$rel" } if [[ "$is_git_repo" -eq 1 ]]; then # Build from tracked files only so accidental local files are excluded. while IFS= read -r rel; do [[ -z "$rel" ]] && continue case "$rel" in Apps/*|category-list.json|recommend-list.json|featured-apps.json) if [[ -f "$repo_root/$rel" ]]; then copy_file "$rel" fi ;; esac done < <(git -C "$repo_root" ls-files) else cp -R "$repo_root/Apps" "$staging_dir/" for file in "${required_root_files[@]}"; do copy_file "$file" done fi # Export compatibility: source uses .yaml, AppStore tooling often expects .yml while IFS= read -r compose_yaml; do compose_yml="${compose_yaml%.yaml}.yml" cp "$compose_yaml" "$compose_yml" rm -f "$compose_yaml" done < <(find "$staging_dir/Apps" -type f -name 'docker-compose.yaml' | sort) if ! find "$staging_dir/Apps" -type f -name 'docker-compose.yml' | grep -q .; then echo "ERROR: no docker-compose.yml files found in exported Apps" exit 1 fi mkdir -p "$output_dir" zip_path="$(cd "$output_dir" && pwd)/$zip_name" ( cd "$staging_dir" zip -rq "$zip_path" . -x "*.DS_Store" "__MACOSX/*" ) if command -v shasum >/dev/null 2>&1; then checksum="$(shasum -a 256 "$zip_path" | awk '{print $1}')" echo "ZIP created: $zip_path" echo "SHA256: $checksum" else echo "ZIP created: $zip_path" fi if [[ "$push_mode" -eq 1 ]]; then if [[ "$is_git_repo" -ne 1 ]]; then echo "ERROR: --push requires a git repository" exit 1 fi echo "Push mode enabled: switching to main and publishing dist/" git -C "$repo_root" checkout main git -C "$repo_root" add dist/ if git -C "$repo_root" diff --cached --quiet; then echo "No staged changes in dist/. Nothing to commit." exit 0 fi git -C "$repo_root" commit -m "Updated appstore" git -C "$repo_root" push origin main fi