From d1a5bab0b9c5ca56bb19b76823216746865be520 Mon Sep 17 00:00:00 2001 From: d12frosted Date: Tue, 27 Jun 2017 21:30:04 +0300 Subject: [PATCH] initial implementation --- Makefile | 24 +++++++++ README.org | 53 +++++++++++++++++++ configure | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++ mirror-elpa | 90 +++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 Makefile create mode 100644 README.org create mode 100755 configure create mode 100755 mirror-elpa diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc00278 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +.PHONY: all install + +# Customisable +SCHEDULE=0 */4 * * * # any valid cron expression +CONFIG_FILE=$(XDG_CONFIG_HOME)/mirror-elpa.sh + +# Non-customisable +path=$(PWD)/mirror-elpa +job:=$(SCHEDULE) $(path) $(CONFIG_FILE) +tmp:=$(shell mktemp) + +all: + # no-op + # specified so that `make && make install` won't accidentally install twice + +install: + -crontab -l 2> /dev/null > $(tmp) + @if grep -Eq "/mirror-elpa $(CONFIG_FILE)$$" $(tmp); then \ + echo "\033[1;33mWARN: crontab already contains a reference to mirror-elpa with $(CONFIG_FILE)."; \ + echo "This is likely to result in the job being run more often than" \ + "expected.\033[0m"; \ + fi + echo "$(job)" >> $(tmp) + crontab $(tmp) diff --git a/README.org b/README.org new file mode 100644 index 0000000..ae4f61f --- /dev/null +++ b/README.org @@ -0,0 +1,53 @@ +* =mirror-elpa= + +(To) Mirror Emacs Lisp package archives. Used to create and maintain mirrors +under Git repository. + +Note that currently only GitHub is supported out of box as a remote for the +mirror. Though you can use =mirror-elpa= to maintain local mirror or mirror on +any other Git hosting. + +** Prerequisites + +The following software must be installed on your system: + +- [[https://stedolan.github.io/jq/][jq]] +- [[http://git-scm.org/][Git]] +- [[http://curl.haxx.se/][cURL]] +- [[https://www.gnu.org/software/emacs/][Emacs]] + +** Usage + +#+BEGIN_SRC bash +$ git clone https://github.com/d12frosted/mirror-elpa +$ cd mirror-elpa +$ ./configure +$ make install +#+END_SRC + +You can also specify how often to sync mirror when running make install +(defaults to every 4 hours) and location to resulting configuration file +(defaults to =$XDG_CONFIG_HOME/mirror-elpa.sh=: + +#+BEGIN_SRC bash +$ make install SCHEDULE="0 0 * * *" CONFIG_FILE="~/mirror-elpa.sh" +#+END_SRC + +=SCHEDULE= can be set to any valid [[https://en.wikipedia.org/wiki/Cron#CRON_expression][CRON expression]]. + +Resulting configuration file can be used for more grained control over +=mirror-elpa= behaviour. + +- Set value of =mirror_path= to avoid using temporary folder for mirror. +- Set value of =mirror_repo_branch_name= to change branch name (default is + =master=). +- Define =git_config_hook= function that is called before changes to repository + are committed. This is a good place to configure Git. +- Set value of =mirror_repo= if you wish to use local mirror or mirror hosted + somewhere else rather than on GitHub. Empty string means local mirror (script + doesn't push). + +** Credits + +- [[https://github.com/dochang][Desmond O. Chang]], thanks for [[https://github.com/dochang/elpa-clone][elpa-clone]]. +- [[https://github.com/ninrod][Filipe Silva]], thanks for keeping this project alive. diff --git a/configure b/configure new file mode 100755 index 0000000..c2d2279 --- /dev/null +++ b/configure @@ -0,0 +1,149 @@ +#!/usr/bin/env bash + +# Exit script on errors +set -ue + +# Read arguments +show_help= +for arg in "$@"; do + case "$arg" in + "-h"|--help) + show_help=stdin + ;; + *) + >&2 echo "Unrecognised argument: $arg" + show_help=stderr + break + ;; + esac +done + +# Display help if necessary +help() { + echo "Usage: $(basename "$0") [options]" + echo " -h, --help print this message and exit" + echo + echo "Report bugs at https://github.com/d12frosted/mirror-elpa/issues" + exit +} +[ "$show_help" = "stdin" ] && help +[ "$show_help" = "stderr" ] && >&2 help + +config_file=$XDG_CONFIG_HOME/mirror-elpa.sh + +printf "Config file location: " +[ -n "$config_file" ] && printf "%s " "$config_file" +read -r i_config_file +[ -z "$i_config_file" ] && i_config_file="$config_file" +[ -n "$i_config_file" ] && config_file="$i_config_file" + +# Declare config variables in case $config_file doesn't +owner= +repo= +name= +email= +message= +access_token= + +[ -f "$config_file" ] && source "$config_file" + +check() { + "$@" > /dev/null 2>&1; + local ret="$?" + if [ "$ret" = 0 ]; then + echo "\033[32;32mok\033[0m" # ok + else + echo "\033[31;31merror\033[0m" + fi + return $ret +} + +while :; do + printf "GitHub repository owner: " + [ -n "$owner" ] && printf "%s " "$owner" + read -r i_owner + [ -z "$i_owner" ] && i_owner="$owner" + [ -n "$i_owner" ] && break +done + +while :; do + printf "GitHub repository name: " + [ -n "$repo" ] && printf "%s " "$repo" + read -r i_repo + [ -z "$i_repo" ] && i_repo="$repo" + [ -n "$i_repo" ] && break +done + +owner="$i_owner" +repo="$i_repo" + +while :; do + echo "mirror-elpa needs to be authorized to access $i_owner/$i_repo." + echo "Please create a token with 'repo' permissions here:" + echo "https://github.com/settings/tokens/new" + echo "Alternatively, hit return without entering anything to have mirror-elpa create one for you." + printf "GitHub API token: " + read -r i_access_token + [ -n "$i_access_token" ] && break + + # Generate an API token + read -rp "Your GitHub username: ($i_owner) " i_auth_user + read -rsp "Your GitHub password: " i_auth_password + echo # output blank line after password + + [ "$i_auth_user" ] || i_auth_user="$i_owner" + + github() { + curl -s "https://api.github.com/$1" \ + -u "$i_auth_user:$i_auth_password" \ + -H "Content-Type: application/json" \ + -H "Accept: application/vnd.github.v3+json" \ + $(shift 1; echo "$@") + } + response=$(github authorizations -X POST -d @- <<-JSON +{ + "scopes": [ + "repo" + ], + "note": "mirror-elpa@$(hostname)$(pwd)", + "note_url": "https://github.com/d12frosted/mirror-elpa" +} +JSON + ) + if echo "$response" | grep '"token"'; then + echo "$response" | jq -r .token | read i_access_token + else + printf "\033[31;31mError: \033[0m" + echo "$response" | jq -r .message + echo "You'll need to create a GitHub API token manually." + fi + + [ -n "$i_access_token" ] && break +done + +[ -n "$i_access_token" ] && access_token="$i_access_token" + +[ -z "$name" ] && name="A Friendly Bot" +printf "Bot name: ($name) " +read i_name +[ -n "$i_name" ] && name="$i_name" + +[ -z "$email" ] && + email="$(echo "$name" | sed "s/ /./g" | tr '[:upper:]' '[:lower:]')@example" +printf "Bot email: ($email) " +read i_email +[ -n "$i_email" ] && email="$i_email" + +[ -z "$message" ] && message="Sync archives" +printf "Commit message: ($message) " +read i_message +[ -n "$i_message" ] && message="$i_message" + +printf "owner=%q\n" "$owner" > "$config_file" +printf "repo=%q\n" "$repo" >> "$config_file" +printf "name=%q\n" "$name" >> "$config_file" +printf "email=%q\n" "$email" >> "$config_file" +printf "message=%q\n" "$message" >> "$config_file" +printf "access_token=%q\n" "$access_token" >> "$config_file" + +echo "Successfully wrote configuration to $config_file" diff --git a/mirror-elpa b/mirror-elpa new file mode 100755 index 0000000..55fa0db --- /dev/null +++ b/mirror-elpa @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +set -ue + +# defaults +mirror_path="" +mirror_repo_remote_name="origin" +mirror_repo_branch_name="master" +elpa_clone_path="$HOME/.cache/elpa-clone" +elpa_clone_url="https://github.com/dochang/elpa-clone.git" +email="" +name="" +access_token="" + +PATH=/usr/local/bin:/usr/bin:/bin + +function git_config_hook { + log "Git config hook" +} + +# load configs +config_file=$1 +[ -f "$config_file" ] && source "$config_file" + +# setup url +MIRROR_REPO="https://${owner}:${access_token}@github.com/${owner}/${repo}.git" + +function log { + echo "[$(date '+%d/%m/%y %H:%M:%S')]" "$@" +} + +function clone { + log "Updating mirror for $2 ($1)" + emacs -l "$elpa_clone_path/elpa-clone.el" -nw --batch --eval="(elpa-clone \"$1\" \"$mirror_path/$2\")" +} + +if [[ "$mirror_path" = "" ]]; then + mirror_path="$(mktemp -d)" +fi + +trap '[ "$?" -eq 0 ] || log Error! Could not update elpa mirrors!' EXIT + +log "Start updating elpa mirrors" +log "mirror_path: $mirror_path" +log "MIRROR_REPO: $MIRROR_REPO" +log "elpa_clone_path: $elpa_clone_path" +log "PATH: $PATH" + +if [[ ! -d $elpa_clone_path ]]; then + log "elpa-clone tool is missing, installing it..." + git clone --depth 1 "$elpa_clone_url" "$elpa_clone_path" +else + log "updating elpa-clone" + cd "$elpa_clone_path" + git fetch origin + git reset --hard origin/master +fi + +if [[ ! -d "$mirror_path" ]]; then + log "cloning mirror repository" + git clone --depth 1 "$MIRROR_REPO" "$mirror_path" +fi + +cd "$mirror_path" + +# if [ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1; then +# log "updating mirror repository" +# git fetch "$mirror_repo_remote_name" +# git reset --hard "${mirror_repo_remote_name}/${mirror_repo_branch_name}" +# fi + +clone "http://orgmode.org/elpa/" "org" +clone "https://elpa.gnu.org/packages/" "gnu" +clone "rsync://melpa.org/packages/" "melpa" +clone "rsync://stable.melpa.org/packages/" "stable-melpa" + +if [ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1; then + log "Configure repository" + git config user.name "$name" + git config user.email "$email" + git_config_hook + git add --all + log "Committing and pushing all changes to mirror repository" + git commit -m "snapshot $(date '+%d/%m/%y %H:%M:%S')" + if [[ $"MIRROR_REPO" != "" ]]; then + git push "$MIRROR_REPO" "$mirror_repo_branch_name" + fi +fi + +log "Done updating elpa mirrors"