grm (8617B)
1 #!/bin/sh 2 # grm: git repo manager for self-hosted git servers 3 4 # This is a customised version of grm. 5 # 6 # The original version can be obtained from: 7 # https://sink.krj.st/grm 8 # 9 # Copyright 2020 by krasjet. 10 # License: https://sink.krj.st/grm/file/LICENSE 11 12 13 #---------------+----------+-----------------# 14 # | config | # 15 # +----------+ # 16 17 # root directory of git repositories 18 GRM_REPOS_ROOT="/home/git" 19 20 # default owner 21 GRM_OWNER="Chris Bracken" 22 23 # default url prefix (without ending slash) 24 GRM_URL_PREFIX="https://git.bracken.jp" 25 26 # Add Mastodon verification URL to index header. 27 ME_URL="https://bsd.network/@cbracken" 28 29 # path of the post-receive hooks for gitout 30 GRM_POSTRECV_HOOK="/home/git/git_infra/post-receive" 31 GRM_POSTRECV_HOOKS_DIR="/home/git/git_infra/post-receive.d" 32 33 # root directory of gitout web pages 34 GITOUT_WEB_ROOT="/usr/local/www/git.bracken.jp" 35 36 # # 37 # # 38 #--------------------------------------------# 39 40 # for gitout 41 export LC_CTYPE="en_US.UTF-8" 42 43 prog_name="${0##*/}" 44 repos_root=${GRM_REPOS_ROOT:-/home/git} 45 web_root=${GITOUT_WEB_ROOT:-/srv/git} 46 47 recompile_repo() { 48 repo_dir="${repos_root}/${1}.git" 49 repo_web_dir="${web_root}/${repo_name}" 50 cachefile="${repo_dir}/.htmlcache" 51 52 [ -d "$repo_dir" ] || { echo "[$1] repo not found"; return 1; } 53 54 if [ -e "$repo_dir/git-daemon-export-ok" ]; then 55 echo "[$1] recompiling gitout pages..." 56 mkdir -p "$repo_web_dir" 57 cd "${repo_web_dir:?}" && \ 58 rm -f "$cachefile" && \ 59 rm -rf "commit" "file" && \ 60 gitout -c "$cachefile" "$repo_dir" && \ 61 ln -sf log.html index.html && \ 62 ln -sf ../logo.png logo.png && \ 63 ln -sf ../style.css style.css && \ 64 ln -sf ../favicon.png favicon.png && \ 65 echo "[$1] done!" 66 else 67 echo "[$1] Not recompiling gitout pages: private repo" 68 fi 69 } 70 71 rebuild_index() { 72 echo "[index] rebuilding index..." 73 mkdir -p "${web_root}" || return 1; 74 # 1. find all directories in $repos_root ending with .git 75 # 2. filter all the public repos (with git-daemon-export-ok) 76 # 3. exclude any repo marked with gitout-no-index 77 # 4. sort the result 78 # 5. hack for posix compatibility 79 # 6. run gitout_index on the result 80 # 7. export result to index.html 81 find "${repos_root}/." ! -name . -prune \ 82 -type d -name "*.git" \ 83 -exec test -e "{}/git-daemon-export-ok" \;\ 84 -exec test ! -e "{}/gitout-no-index" \; \ 85 -print \ 86 | sort -f \ 87 | sed -e 's/"/"\\""/g' -e 's/.*/"&"/' \ 88 | xargs gitout_index -m "${ME_URL}" \ 89 > "${web_root}/index.html" && \ 90 echo "[index] done!" 91 } 92 93 RED="\033[91m" 94 GREEN="\033[92m" 95 YELLOW="\033[93m" 96 BLUE="\033[94m" 97 RESET="\033[0m" 98 99 grm_new() { 100 set -e 101 default_owner=${GRM_OWNER:-$(logname)} 102 url_prefix=${GRM_URL_PREFIX:-git://$(hostname -f)} 103 default_desc="a work in progress" 104 105 printf "%b\n> " "${BLUE}repo name (without trailing .git)${RESET}" 106 read -r repo_name 107 [ -z "$repo_name" ] && \ 108 { echo "no repo name given, exiting..."; exit 1; } 109 110 # now we have the complete path of the repo 111 repo_path="$repos_root/${repo_name}.git" 112 [ -e "$repo_path" ] && \ 113 { echo "repository already exists"; exit 1; } 114 115 printf "%b\n> " "${YELLOW}visibility: 116 1) public\n 2) private\n 3) unlisted (hide from index) 117 ${BLUE}enter index [default: ${GREEN}1${BLUE}]${RESET}" 118 read -r visibility 119 120 case $visibility in 121 1|public) exported=1 ;; 122 2|private) hidden=1 ;; 123 3|unlisted) exported=1; hidden=1 ;; 124 *) printf "%b\n" "${YELLOW}visibility defaults to ${GREEN}public${RESET}" 125 exported=1 ;; 126 esac 127 128 printf "%b%s%b\n> " "${BLUE}description [${GREEN}" "$default_desc" "${BLUE}]${RESET}" 129 read -r repo_desc 130 repo_desc=${repo_desc:-$default_desc} 131 132 printf "%b%s%b\n> " "${BLUE}owner [${GREEN}" "$default_owner" "${BLUE}]${RESET}" 133 read -r owner 134 owner=${owner:-$default_owner} 135 136 printf "%b%s%b\n> " \ 137 "${BLUE}clone url [${GREEN}" "$url_prefix/$repo_name.git" "${BLUE}]${RESET}" 138 read -r clone_url 139 clone_url=${clone_url:-$url_prefix/$repo_name}.git 140 141 # start creating repo 142 git init --bare "$repo_path" 143 144 echo "writing gitout metadata..." 145 printf "%s\n" "$repo_desc" > "$repo_path/description" 146 printf "%s\n" "$owner" > "$repo_path/owner" 147 printf "%s\n" "$clone_url" > "$repo_path/url" 148 149 echo "setting visibility..." 150 [ "$exported" = "1" ] && : >> "$repo_path/git-daemon-export-ok" 151 [ "$hidden" = "1" ] && : >> "$repo_path/gitout-no-index" 152 153 echo "installing gitout post-receive hook..." 154 ln -sf "$GRM_POSTRECV_HOOK" "$repo_path/hooks/post-receive" 155 mkdir -p "$repo_path/hooks/post-receive.d" 156 for hook in "$(ls "$GRM_POSTRECV_HOOKS_DIR")"; do 157 ln -sf "$GRM_POSTRECV_HOOKS_DIR/$hook" "$repo_path/hooks/post-receive.d/$hook" 158 done 159 160 echo "done!" 161 } 162 163 grm_remove() { 164 [ $# -gt 0 ] || { echo "no repo name given, exiting..."; exit 1; } 165 166 for repo in "$@" 167 do 168 printf "remove %s? [y/N] " "$repo" 169 read -r resp 170 if echo "$resp" | grep -iq "^y$"; then 171 rm -rf "${repos_root:?}/${repo:?}.git" || continue; 172 rm -rf "${web_root:?}/${repo:?}" || continue; 173 fi 174 done 175 # only rebuild index if gitout exists 176 command -v gitout_index -m "${ME_URL}" >/dev/null && rebuild_index & 177 wait 178 } 179 180 grm_list() { 181 case "$1" in 182 public) 183 find "${repos_root}/." ! -name . -prune \ 184 -type d -name "*.git" \ 185 -exec test -e "{}/git-daemon-export-ok" \; \ 186 -exec test ! -e "{}/gitout-no-index" \; \ 187 -exec basename {} '.git' \; | sort -f ;; 188 private) 189 find "${repos_root}/." ! -name . -prune \ 190 -type d -name "*.git" \ 191 -exec test ! -e "{}/git-daemon-export-ok" \; \ 192 -exec basename {} '.git' \; | sort -f ;; 193 hidden|unlisted) 194 find "${repos_root}/." ! -name . -prune \ 195 -type d -name "*.git" \ 196 -exec test -e "{}/git-daemon-export-ok" \; \ 197 -exec test -e "{}/gitout-no-index" \; \ 198 -exec basename {} '.git' \; | sort -f ;; 199 *) 200 find "${repos_root}/." ! -name . -prune \ 201 -type d -name "*.git" \ 202 -exec basename {} '.git' \; | sort -f ;; 203 esac 204 } 205 206 grm_recompile() { 207 for repo_name in "$@" 208 do 209 recompile_repo "$repo_name" & 210 done 211 rebuild_index & 212 wait 213 echo "recompilation done!" 214 } 215 216 grm_recompileall() { 217 grm_list \ 218 | sed -e 's/"/"\\""/g' -e 's/.*/"&"/' \ 219 | xargs "$0" rc 220 } 221 222 grm_info() { 223 [ -z "$1" ] && { echo "no repo name given, exiting..."; exit 1; } 224 225 repo_name=$1 226 repo_dir="${repos_root}/${repo_name}.git" 227 228 [ -d "$repo_dir" ] || { echo "can't find repo named $repo_name"; exit 1; } 229 echo "name: $repo_name" 230 printf "visibility: " 231 232 if [ -e "${repo_dir}/git-daemon-export-ok" ]; then 233 if [ -e "${repo_dir}/gitout-no-index" ]; then 234 printf "%b\n" "${YELLOW}unlisted${RESET}" 235 else 236 printf "%b\n" "${GREEN}public${RESET}" 237 fi 238 else 239 printf "%b\n" "${RED}private${RESET}" 240 fi 241 242 [ -f "${repo_dir}/description" ] && \ 243 echo "description: $(cat "${repo_dir}/description")" 244 245 [ -f "${repo_dir}/owner" ] && \ 246 echo "owner: $(cat "${repo_dir}/owner")" 247 248 [ -f "${repo_dir}/url" ] && \ 249 echo "url: $(cat "${repo_dir}/url")" 250 } 251 252 show_help() { 253 cat << EOF 254 usage: $prog_name <command> [<args>] 255 256 Git repo manager, manage git repositories on self-hosted git servers. 257 258 If you have created a 'git' user for managing git repositories, this 259 script should be run as: 260 $ doas -u git -- $prog_name <command> [<args>] 261 or 262 $ sudo -u git -- $prog_name <command> [<args>] 263 264 commands: 265 new create a new repo 266 info repo_name display metadata of the repo 267 ls list all repos 268 ls public list public repos 269 ls private list private repos 270 ls unlisted list unlisted (hidden) repos 271 rm repo1 [repo2..] remove repos 272 rc recompile gitout index 273 rc repo1 [repo2..] recompile gitout pages for repos, 274 and recompile index 275 rca recompile all public repos 276 help show help 277 EOF 278 } 279 280 # parse subcommands 281 case "$1" in 282 new) cmd=new;; 283 ls|list) cmd=list;; 284 rm|remove) cmd=remove;; 285 rc|recompile) cmd=recompile;; 286 rca|recompileall) cmd=recompileall;; 287 info) cmd=info;; 288 *) { show_help; exit; };; 289 esac 290 291 shift 292 grm_"$cmd" "$@"