Git Repositories

Add some sanity checks to the git scripts to prevent root user to run it directly.
[simple-git-host.git] / homegit / gitrepo.sh
1 #!/bin/sh
2 # vim: et ai cin sw=2 ts=2 tw=0:
3 [ "$HOME" = $(dirname $(readlink -f "$0")) ] || exit 255
4 cd ~/
5
6 usage() {
7   cat <<EOF
8 $0 Action Parameters
9 Action is one of:
10  - create Name [Description]
11  - destroy Name
12  - get Name Option
13    Option can be
14    * 'description'
15    * a git configuration option
16  - set Name Option Value
17  - list-users
18  - create-user Username Password [Key]
19    Password should be in md5
20  - change-user Username Password
21  - show-pwd Username
22  - destroy-user Username
23  - show-users Name
24  - add-user Name Username
25    Username should already exists
26  - del-user Name Username
27  - list-keys Username
28  - add-key Username Key
29  - del-key Username Key
30    del-key Username Position
31    Position starts from 1 as listed by list-keys
32  - graph Name
33    Show a graphical representation of the repository.
34  - fetch Name Url
35    Fetch all remotes if the Url is in Name.git/fetchurls file
36  - export Name on|off
37    Export (or not) the following repo as read-only for git-daemon
38  - sync Name from|to url
39    Synchronize from or to the following URL.
40    Sync to is done automatically at each commit. Be careful to add git user ssl keys to remote repo.
41    Sync from is done automatically after a POST request to post-update.php script with a json variable named 'payload' containing:
42      {'repository': {'name', 'url'}
43  - unsync Name from|to url
44    remove sync previously set by 'sync' command.
45  - listsync Name from|to
46    list the url that are synchronized from or to, as defined by 'sync' command.
47 EOF
48 }
49
50 checkparams() {
51   while [ -n "$1" ]; do
52     param="$1"
53     eval value="\$$param"
54     if [ -z "$value" ]; then
55       echo "$param is missing" >&2
56       exit 1
57     fi
58     shift
59   done
60 }
61
62 check_repo() {
63   REPO="$1"
64   if echo "$REPO" | grep -q '[^-_a-zA-Z0-9]'; then
65     echo "Repository name can only contains letters, numbers, hyphen and underscore" >&2
66     exit 2
67   fi
68 }
69
70 check_username() {
71   NAME="$1"
72   if echo "$NAME" | grep -q '[^-_a-zA-Z0-9]'; then
73     echo "User name can only contains letters, numbers, hyphen and underscore" >&2
74     exit 2
75   fi
76 }
77
78 create_repo() {
79   REPO="$1"
80   DESC="$2"
81   check_repo "$REPO"
82   if [ -d "$REPO".git ]; then
83     echo "$REPO already exists." >&2
84     exit 2
85   fi
86   mkdir "$REPO".git
87   (
88     cd "$REPO".git
89     git --bare init
90     git config core.sharedRepository 1
91     if [ -n "$DESC" ]; then
92       echo "$DESC" > description
93     fi
94   )
95 }
96
97 destroy_repo() {
98   REPO="$1"
99   check_repo "$REPO"
100   if [ ! -d "$REPO".git ]; then
101     echo "$REPO does not exist." >&2
102     exit 2
103   fi
104   rm -rf "$REPO".git
105 }
106
107 get_option() {
108   REPO="$1"
109   OPTION="$2"
110   check_repo "$REPO"
111   if [ ! -d "$REPO".git ]; then
112     echo "$REPO does not exist." >&2
113     exit 2
114   fi
115   if [ "$OPTION" = "description" ]; then
116     cat "$REPO".git/description
117   else
118     (
119       cd "$REPO".git
120       git --bare config "$OPTION"
121     )
122   fi
123 }
124
125 set_option() {
126   REPO="$1"
127   OPTION="$2"
128   OPT_VAL="$3"
129   check_repo "$REPO"
130   if [ ! -d "$REPO".git ]; then
131     echo "$REPO does not exist." >&2
132     exit 2
133   fi
134   if [ "$OPTION" = "description" ]; then
135     echo "$OPT_VAL" > "$REPO".git/description
136   else
137     (
138       cd "$REPO".git
139       if [ -n "$OPT_VAL" ]; then
140         git --bare config "$OPTION" "$OPT_VAL"
141       else
142         git --bare config --unset "$OPTION"
143       fi
144     )
145   fi
146 }
147
148 list_users() {
149   for f in .keys/*.pwd; do
150     basename $f .pwd
151   done
152 }
153
154 create_user() {
155   USERNAME="$1"
156   PASSWD="$2"
157   KEY="$3"
158   check_username "$USERNAME"
159   if [ -f .keys/$USERNAME.pwd ]; then
160     echo "$USERNAME already exists." >&2
161     exit 2
162   fi
163   mkdir -p .keys
164   echo "$PASSWD" > .keys/$USERNAME.pwd
165   if [ -n "$KEY" ]; then
166     echo "$KEY" > .keys/$USERNAME.keys
167   else
168     touch .keys/$USERNAME.keys
169   fi
170   ./makekeys.sh
171 }
172
173 change_user() {
174   USERNAME="$1"
175   PASSWD="$2"
176   check_username "$USERNAME"
177   if [ ! -f .keys/$USERNAME.pwd ]; then
178     echo "$USERNAME does not exists." >&2
179     exit 2
180   fi
181   mkdir -p .keys
182   echo "$PASSWD" > .keys/$USERNAME.pwd
183 }
184
185 show_pwd() {
186   USERNAME="$1"
187   check_username "$USERNAME"
188   if [ ! -f .keys/$USERNAME.pwd ]; then
189     echo "$USERNAME does not exists." >&2
190     exit 2
191   fi
192   cat .keys/$USERNAME.pwd
193 }
194
195 destroy_user() {
196   USERNAME="$1"
197   check_username "$USERNAME"
198   if [ ! -f .keys/$USERNAME.pwd ]; then
199     echo "$USERNAME does not exists." >&2
200     exit 2
201   fi
202   rm .keys/$USERNAME.*
203   for p in ?*.git; do
204     [ -e $p/.users ] && sed -i "/^$USERNAME\$/d" $p/.users
205   done
206   ./makekeys.sh
207 }
208
209 add_user() {
210   REPO="$1"
211   USERNAME="$2"
212   check_repo "$REPO"
213   check_username "$USERNAME"
214   if [ ! -d "$REPO".git ]; then
215     echo "$REPO does not exist." >&2
216     exit 2
217   fi
218   if [ ! -f .keys/$USERNAME.pwd ]; then
219     echo "$USERNAME does not exists." >&2
220     exit 2
221   fi
222   [ -e "$REPO".git/.users ] || touch "$REPO".git/.users
223   grep -q "^$USERNAME\$" "$REPO".git/.users || echo "$USERNAME" >> "$REPO".git/.users
224 }
225
226 del_user() {
227   REPO="$1"
228   USERNAME="$2"
229   check_repo "$REPO"
230   check_username "$USERNAME"
231   if [ ! -d "$REPO".git ]; then
232     echo "$REPO does not exist." >&2
233     exit 2
234   fi
235   if [ -e "$REPO".git/.users ]; then
236     grep -q "^$USERNAME\$" "$REPO".git/.users && sed -i "/^$USERNAME\$/d" "$REPO".git/.users
237   fi
238 }
239
240 show_users() {
241   REPO="$1"
242   check_repo "$REPO"
243   if [ ! -d "$REPO".git ]; then
244     echo "$REPO does not exist." >&2
245     exit 2
246   fi
247   if [ -f "$REPO".git/.users ]; then
248     cat "$REPO".git/.users
249   fi
250 }
251
252 list_keys() {
253   USERNAME="$1"
254   check_username "$USERNAME"
255   if [ ! -f .keys/$USERNAME.pwd ]; then
256     echo "$USERNAME does not exists." >&2
257     exit 2
258   fi
259   cat .keys/$USERNAME.keys
260 }
261
262 add_key() {
263   USERNAME="$1"
264   KEY="$2"
265   check_username "$USERNAME"
266   if [ ! -f .keys/$USERNAME.pwd ]; then
267     echo "$USERNAME does not exists." >&2
268     exit 2
269   fi
270   echo "$KEY" >> .keys/$USERNAME.keys
271   ./makekeys.sh
272 }
273
274 del_key() {
275   USERNAME="$1"
276   KEY="$2"
277   check_username "$USERNAME"
278   if [ ! -f .keys/$USERNAME.pwd ]; then
279     echo "$USERNAME does not exists." >&2
280     exit 2
281   fi
282   if echo "$KEY" | grep -q "^[0-9]\+$"; then
283     if [ $KEY -eq 1 ]; then
284       sed -i -n '2,$p' .keys/$USERNAME.keys
285     else
286       prev=$(($KEY - 1))
287       next=$(($KEY + 1))
288       sed -i -n "1,${prev}p; ${next},\$p" .keys/$USERNAME.keys
289     fi
290   else
291     sed -i "/^$KEY\$/d" .keys/$USERNAME.keys
292   fi
293   ./makekeys.sh
294 }
295
296 graph() {
297   NAME="$1"
298   check_repo "$NAME"
299   if [ ! -d "$NAME".git ]; then
300     echo "$NAME does not exist." >&2
301     exit 2
302   fi
303   (
304     cd "$NAME".git
305     git log --all --oneline --graph --decorate=short
306   )
307 }
308
309 fetch() {
310   NAME="$1"
311   URL="$2"
312   check_repo "$NAME"
313   if [ -f "$NAME".git/fetchremotes ] && grep -q -F "$URL" "$NAME".git/fetchremotes; then
314     (
315       cd "$NAME".git
316       git fetch "$URL"
317     )
318   fi
319 }
320
321 exportRepo() {
322   NAME="$1"
323   DOEXPORT="$2"
324   check_repo "$NAME"
325   if [ "$DOEXPORT" = "on" ]; then
326     touch "$NAME".git/git-daemon-export-ok
327   else
328     rm -f "$NAME".git/git-daemon-export-ok
329   fi
330 }
331
332 syncRepo() {
333   NAME="$1"
334   DIR="$2"
335   URL="$3"
336   check_repo "$NAME"
337   if [ "$DIR" = "to" ]; then
338     syncToRepo "$NAME" "$URL"
339   elif [ "$DIR" = "from" ]; then
340     syncFromRepo "$NAME" "$URL"
341   else
342     echo "$DIR is not a correct sync direction. 'to' or 'from' expected." >&2
343     exit 2
344   fi
345 }
346
347 syncToRepo() {
348   NAME="$1"
349   URL="$2"
350   check_repo "$NAME"
351   HOST=$(echo -n "$URL"|md5sum|cut -d' ' -f1)
352   (
353     cd "$NAME".git
354     git remote add --mirror $HOST "$URL"
355   )
356   if [ ! -f "$NAME".git/hooks/post-update ]; then
357     cat <<'EOF' > "$NAME".git/hooks/post-update
358 #!/bin/sh
359 if [ -d hooks/.post-update.d ]; then
360   for f in hooks/.post-update.d/*; do 
361     [ -x "$f" ] && ./"$f"
362   done
363 fi
364 EOF
365     chmod +x "$NAME".git/hooks/post-update
366     mkdir -p "$NAME".git/hooks/.post-update.d
367   fi
368   echo "git push --quiet $HOST &" > "$NAME".git/hooks/.post-update.d/$HOST
369   chmod +x "$NAME".git/hooks/.post-update.d/$HOST
370 }
371
372 syncFromRepo() {
373   NAME="$1"
374   URL="$2"
375   check_repo "$NAME"
376   echo "$URL" >> "$NAME".git/fetchremotes
377 }
378
379 unsyncRepo() {
380   NAME="$1"
381   DIR="$2"
382   URL="$3"
383   check_repo "$NAME"
384   if [ "$DIR" = "to" ]; then
385     unsyncToRepo "$NAME" "$URL"
386   elif [ "$DIR" = "from" ]; then
387     unsyncFromRepo "$NAME" "$URL"
388   else
389     echo "$DIR is not a correct sync direction. 'to' or 'from' expected." >&2
390     exit 2
391   fi
392 }
393
394 unsyncToRepo() {
395   NAME="$1"
396   URL="$2"
397   check_repo "$NAME"
398   HOST=$(echo -n "$URL"|md5sum|cut -d' ' -f1)
399   (
400     cd "$NAME".git
401     git remote rm $HOST
402   )
403   [ -f "$NAME".git/hooks/.post-update.d/$HOST ] && rm "$NAME".git/hooks/.post-update.d/$HOST
404   true
405 }
406
407 unsyncFromRepo() {
408   NAME="$1"
409   URL="$2"
410   check_repo "$NAME"
411   [ -f "$NAME".git/fetchremotes ] && sed -i -n "\,$URL,d; p" "$NAME".git/fetchremotes
412   true
413 }
414
415 listSyncRepo() {
416   NAME="$1"
417   DIR="$2"
418   check_repo "$NAME"
419   if [ "$DIR" = "to" ]; then
420     (
421       cd "$NAME".git
422       for r in $(git remote); do
423         git config remote.$r.url
424       done
425     )
426   elif [ "$DIR" = "from" ]; then
427     if [ -f "$NAME".git/fetchremotes ]; then
428       cat "$NAME".git/fetchremotes
429     else
430       true
431     fi
432   else
433     echo "$DIR is not a correct sync direction. 'to' or 'from' expected." >&2
434     exit 2
435   fi
436 }
437
438 ACTION=''
439 NAME=''
440 REPO=''
441 USERNAME=''
442 PASSWD=''
443 DESC=''
444 OPTION=''
445 OPT_VAL=''
446 KEY=''
447 URL=''
448 EXPORTREPO=''
449 SYNCDIRECTION=''
450 while [ -n "$1" ]; do
451   case "$1" in
452     -h|--help)
453       usage
454       exit 0
455       ;;
456     *)
457       if [ -z "$ACTION" ]; then
458         if echo "$1" | grep -q '^\(create\|destroy\|get\|set\|list-users\|create-user\|change-user\|show-pwd\|destroy-user\|show-users\|add-user\|del-user\|list-keys\|add-key\|del-key\|graph\|fetch\|export\|sync\|unsync\|listsync\)$'; then
459           ACTION="$1"
460           shift
461         else
462           echo "Unrecognized action ($1)" >&2
463           exit 1
464         fi
465       else
466         if [ "$ACTION" = "list-users" ]; then
467           echo "Unrecognized parameter ($1)" >&2
468           exit 1
469         elif [ -z "$NAME" ]; then
470           NAME="$1"
471           shift
472         else
473           if [ "$ACTION" = "create" ]; then
474             if [ -z "$DESC" ]; then
475               DESC="$1"
476               shift
477             else
478               echo "Unrecognized parameter ($1)" >&2
479               exit 1
480             fi
481           elif [ "$ACTION" = "destroy" ]; then
482             echo "Unrecognized parameter ($1)" >&2
483             exit 1
484           elif [ "$ACTION" = "get" ] || [ "$ACTION" = "set" ]; then
485             if [ -z "$OPTION" ]; then
486               OPTION="$1"
487               shift
488             elif [ "$ACTION" = "set" ] && [ -z "$OPT_VAL" ]; then
489               OPT_VAL="$1"
490               shift
491             else
492               echo "Unrecognized parameter ($1)" >&2
493               exit 1
494             fi
495           elif [ "$ACTION" = "create-user" ]; then
496             if [ -z "$PASSWD" ]; then
497               PASSWD="$1"
498               shift
499             elif [ -z "$KEY" ]; then
500               KEY="$1"
501               shift
502             else
503               echo "Unrecognized parameter ($1)" >&2
504               exit 1
505             fi
506           elif [ "$ACTION" = "change-user" ]; then
507             if [ -z "$PASSWD" ]; then
508               PASSWD="$1"
509               shift
510             else
511               echo "Unrecognized parameter ($1)" >&2
512               exit 1
513             fi
514           elif [ "$ACTION" = "show-pwd" ]; then
515             echo "Unrecognized parameter ($1)" >&2
516             exit 1
517           elif [ "$ACTION" = "destroy-user" ]; then
518             echo "Unrecognized parameter ($1)" >&2
519             exit 1
520           elif [ "$ACTION" = "show-users" ]; then
521             echo "Unrecognized parameter ($1)" >&2
522             exit 1
523           elif [ "$ACTION" = "add-user" ] || [ "$ACTION" = "del-user" ]; then
524             if [ -z "$USERNAME" ]; then
525               USERNAME="$1"
526               shift
527             else
528               echo "Unrecognized parameter ($1)" >&2
529               exit 1
530             fi
531           elif [ "$ACTION" = "list-keys" ]; then
532             echo "Unrecognized parameter ($1)" >&2
533             exit 1
534           elif [ "$ACTION" = "add-key" ] || [ "$ACTION" = "del-key" ]; then
535             if [ -z "$KEY" ]; then
536               KEY="$1"
537               shift
538             else
539               echo "Unrecognized parameter ($1)" >&2
540               exit 1
541             fi
542           elif [ "$ACTION" = "graph" ]; then
543             echo "Unrecognized parameter ($1)" >&2
544             exit 1
545           elif [ "$ACTION" = "fetch" ]; then
546             if [ -z "$URL" ]; then
547               URL="$1"
548               shift
549             else
550               echo "Unrecognized parameter ($1)" >&2
551               exit 1
552             fi
553           elif [ "$ACTION" = "export" ]; then
554             if [ -z "$EXPORTREPO" ]; then
555               EXPORTREPO="$1"
556               shift
557             else
558               echo "Unrecognized parameter ($1)" >&2
559               exit 1
560             fi
561           elif [ "$ACTION" = "sync" ] || [ "$ACTION" = "unsync" ]; then
562             if [ -z "$SYNCDIRECTION" ]; then
563               SYNCDIRECTION="$1"
564               shift
565             elif [ -z "$URL" ]; then
566               URL="$1"
567               shift
568             else
569               echo "Unrecognized parameter ($1)" >&2
570               exit 1
571             fi
572           elif [ "$ACTION" = "listsync" ]; then
573             if [ -z "$SYNCDIRECTION" ]; then
574               SYNCDIRECTION="$1"
575               shift
576             else
577               echo "Unrecognized parameter ($1)" >&2
578               exit 1
579             fi
580           fi
581         fi
582       fi
583       ;;
584   esac
585 done
586 case "$ACTION" in
587   "")
588     usage
589     exit 1
590     ;;
591   create)
592     REPO="$NAME"
593     checkparams REPO
594     create_repo "$REPO" "$DESC"
595     ;;
596   destroy)
597     REPO="$NAME"
598     checkparams REPO
599     destroy_repo "$REPO"
600     ;;
601   get)
602     REPO="$NAME"
603     checkparams REPO OPTION
604     get_option "$REPO" "$OPTION"
605     ;;
606   set)
607     REPO="$NAME"
608     checkparams REPO OPTION
609     set_option "$REPO" "$OPTION" "$OPT_VAL"
610     ;;
611   list-users)
612     list_users "$REPO"
613     ;;
614   create-user)
615     USERNAME="$NAME"
616     checkparams USERNAME PASSWD
617     create_user "$USERNAME" "$PASSWD" "$KEY"
618     ;;
619   change-user)
620     USERNAME="$NAME"
621     checkparams USERNAME PASSWD
622     change_user "$USERNAME" "$PASSWD"
623     ;;
624   show-pwd)
625     USERNAME="$NAME"
626     checkparams USERNAME
627     show_pwd "$USERNAME"
628     ;;
629   destroy-user)
630     USERNAME="$NAME"
631     checkparams USERNAME
632     destroy_user "$USERNAME"
633     ;;
634   show-users)
635     REPO="$NAME"
636     checkparams REPO
637     show_users "$REPO"
638     ;;
639   add-user)
640     REPO="$NAME"
641     checkparams REPO USERNAME
642     add_user "$REPO" "$USERNAME"
643     ;;
644   del-user)
645     REPO="$NAME"
646     checkparams REPO USERNAME
647     del_user "$REPO" "$USERNAME"
648     ;;
649   list-keys)
650     USERNAME="$NAME"
651     checkparams USERNAME
652     list_keys "$USERNAME"
653     ;;
654   add-key)
655     USERNAME="$NAME"
656     checkparams USERNAME KEY
657     add_key "$USERNAME" "$KEY"
658     ;;
659   del-key)
660     USERNAME="$NAME"
661     checkparams USERNAME KEY
662     del_key "$USERNAME" "$KEY"
663     ;;
664   graph)
665     REPO="$NAME"
666     checkparams REPO
667     graph "$REPO"
668     ;;
669   fetch)
670     REPO="$NAME"
671     checkparams REPO URL
672     fetch "$REPO" "$URL"
673     ;;
674   export)
675     REPO="$NAME"
676     checkparams REPO EXPORTREPO
677     exportRepo "$REPO" "$EXPORTREPO"
678     ;;
679   sync)
680     REPO="$NAME"
681     checkparams REPO SYNCDIRECTION URL
682     syncRepo "$REPO" "$SYNCDIRECTION" "$URL"
683     ;;
684   unsync)
685     REPO="$NAME"
686     checkparams REPO SYNCDIRECTION URL
687     unsyncRepo "$REPO" "$SYNCDIRECTION" "$URL"
688     ;;
689   listsync)
690     REPO="$NAME"
691     checkparams REPO SYNCDIRECTION
692     listSyncRepo "$REPO" "$SYNCDIRECTION"
693     ;;
694 esac