#!/usr/bin/env bash # Install a release of lutinglt/gitea-github-theme into Gitea asset directories. set -euo pipefail readonly REPOSITORY="lutinglt/gitea-github-theme" readonly BASE_ARCHIVE="theme-github.tar.gz" readonly TEMPLATES_ARCHIVE="theme-github-templates.tar.gz" STAGING_DIR="" require_command() { command -v "$1" >/dev/null 2>&1 || { printf 'Required command not found: %s\n' "$1" >&2 exit 1 } } download_archive() { local archive_name="$1" local release_path="$2" local destination="$3" local url="https://github.com/${REPOSITORY}/releases/${release_path}/${archive_name}" printf 'Downloading %s...\n' "$archive_name" curl --fail --location --retry 3 --output "$destination" "$url" } prepare_directory() { local directory="$1" if [[ -z "$directory" ]]; then printf 'A destination directory is required.\n' >&2 exit 1 fi if [[ -e "$directory" && ! -d "$directory" ]]; then printf 'Destination exists but is not a directory: %s\n' "$directory" >&2 exit 1 fi mkdir -p "$directory" directory="$(cd "$directory" && pwd -P)" if [[ "$directory" == "/" ]]; then printf 'Refusing to use the filesystem root as a destination.\n' >&2 exit 1 fi printf '%s\n' "$directory" } default_manifest_path() { local css_destination="$1" local templates_destination="$2" local state_directory manifest_key if [[ -n "${GITEA_GITHUB_THEME_STATE_DIR:-}" ]]; then state_directory="$GITEA_GITHUB_THEME_STATE_DIR" elif [[ $EUID -eq 0 ]]; then state_directory="/var/lib/gitea-github-theme" else state_directory="${XDG_STATE_HOME:-${HOME:-.}/.local/state}/gitea-github-theme" fi manifest_key="$(printf '%s\0%s' "$css_destination" "$templates_destination" | sha256sum | awk '{print $1}')" printf '%s/%s.manifest\n' "$state_directory" "$manifest_key" } remove_theme_files() { local css_destination="$1" local templates_destination="$2" local manifest="$3" local file_type relative_path destination if [[ -f "$manifest" ]]; then while IFS=$'\t' read -r file_type relative_path; do case "$file_type" in css) destination="$css_destination" ;; template) destination="$templates_destination" ;; *) continue ;; esac # The manifest is generated locally, but still reject paths that can escape a destination. if [[ -z "$relative_path" || "$relative_path" == /* || "$relative_path" == ".." || \ "$relative_path" == ../* || "$relative_path" == */../* || "$relative_path" == */.. ]]; then continue fi rm -f -- "$destination/$relative_path" done < "$manifest" rm -f -- "$manifest" fi # Also remove CSS installed by older versions of this script, which had no manifest. find "$css_destination" -maxdepth 1 -type f -name 'theme-github-*.css' -delete } write_manifest() { local manifest="$1" local base_source="$2" local templates_source="$3" local temporary_manifest file mkdir -p "$(dirname "$manifest")" temporary_manifest="$(mktemp "${manifest}.XXXXXX")" { while IFS= read -r -d '' file; do printf 'css\t%s\n' "${file#"$base_source"/}" done < <(find "$base_source" -type f -print0) while IFS= read -r -d '' file; do printf 'template\t%s\n' "${file#"$templates_source"/}" done < <(find "$templates_source" -type f -print0) } > "$temporary_manifest" mv -f -- "$temporary_manifest" "$manifest" } main() { local version css_destination templates_destination manifest release_path require_command find require_command sha256sum require_command awk if [[ "${1:-}" == "--print-manifest-path" ]]; then css_destination="$(prepare_directory "${2:-}")" templates_destination="$(prepare_directory "${3:-}")" default_manifest_path "$css_destination" "$templates_destination" return fi if [[ "${1:-}" == "--remove" ]]; then css_destination="$(prepare_directory "${2:-}")" templates_destination="$(prepare_directory "${3:-}")" manifest="${4:-$(default_manifest_path "$css_destination" "$templates_destination")}" remove_theme_files "$css_destination" "$templates_destination" "$manifest" printf 'Removed installed Gitea GitHub theme files.\n' return fi require_command curl require_command tar require_command mktemp version="${1:-}" if [[ -z "$version" ]]; then read -r -p "Theme version (for example v1.27.0, or latest) [latest]: " version fi version="${version:-latest}" version="${version#v}" css_destination="${2:-}" if [[ -z "$css_destination" ]]; then read -r -p "CSS destination (for example /path/to/gitea/public/assets/css): " css_destination fi css_destination="$(prepare_directory "$css_destination")" templates_destination="${3:-}" if [[ -z "$templates_destination" ]]; then read -r -p "Templates destination (for example /path/to/gitea/templates): " templates_destination fi templates_destination="$(prepare_directory "$templates_destination")" manifest="${4:-$(default_manifest_path "$css_destination" "$templates_destination")}" if [[ "$version" == "latest" ]]; then release_path="latest/download" else release_path="download/v${version}" fi STAGING_DIR="$(mktemp -d)" trap 'rm -rf -- "$STAGING_DIR"' EXIT download_archive "$BASE_ARCHIVE" "$release_path" "$STAGING_DIR/$BASE_ARCHIVE" download_archive "$TEMPLATES_ARCHIVE" "$release_path" "$STAGING_DIR/$TEMPLATES_ARCHIVE" # Verify the archives and their expected top-level directories before changing Gitea files. mkdir -p "$STAGING_DIR/base" "$STAGING_DIR/templates" tar -xzf "$STAGING_DIR/$BASE_ARCHIVE" -C "$STAGING_DIR/base" tar -xzf "$STAGING_DIR/$TEMPLATES_ARCHIVE" -C "$STAGING_DIR/templates" if [[ ! -d "$STAGING_DIR/base/dist" || ! -d "$STAGING_DIR/templates/templates" ]]; then printf 'Downloaded archives do not have the expected dist/ and templates/ layout.\n' >&2 exit 1 fi printf 'Replacing GitHub theme files...\n' remove_theme_files "$css_destination" "$templates_destination" "$manifest" tar -xzf "$STAGING_DIR/$BASE_ARCHIVE" --strip-components=1 -C "$css_destination" tar -xzf "$STAGING_DIR/$TEMPLATES_ARCHIVE" --strip-components=1 -C "$templates_destination" write_manifest "$manifest" "$STAGING_DIR/base/dist" "$STAGING_DIR/templates/templates" printf 'Installed Gitea GitHub theme %s; manifest written to %s.\n' "$version" "$manifest" } main "$@"