Files
snippets/ggt/install-gitea-github-theme.sh
T
2026-08-23 18:52:19 +07:00

192 lines
6.3 KiB
Bash

#!/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 "$@"