~gitpocket/gitpocket

4
2d63c3140660ed2dd64391f9e78800cc4fc6b7e3
mainfeed-discovery
gitpocket/main.go -rw-r--r--2.8 KiB ViewLogBlameView rawPermalink 2d63c31406 feat(basin): squash and rebase merges, collaborators, quota 1 months ago
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

func main() {
	root := flag.String("root", "/srv/basin", "data directory holding basin.db and repos/")
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, `usage: basin [-root dir] command

  serve [-listen addr] [-host name] [-open] [-ssh-user name]   run the web server
  keys user home type key                     sshd AuthorizedKeysCommand
  shell user                                  forced command for ssh git access
  invite                                      print a single use registration code
  quota [size]                                show or set the per user disk quota, 0 disables
  user name                                   create a user, password read from stdin`)
	}
	flag.Parse()
	args := flag.Args()
	if len(args) == 0 {
		flag.Usage()
		os.Exit(2)
	}
	switch args[0] {
	case "serve":
		serve(*root, args[1:])
	case "keys":
		binary, _ := os.Executable()
		os.Exit(keys(*root, binary, args[1:]))
	case "shell":
		if len(args) != 2 {
			os.Exit(2)
		}
		os.Exit(shell(*root, args[1]))
	case "quota":
		if len(args) == 2 {
			n, err := parseSize(args[1])
			if err != nil {
				log.Fatal(err)
			}
			if err := os.WriteFile(filepath.Join(*root, "quota"), []byte(strconv.FormatInt(n, 10)+"\n"), 0o644); err != nil {
				log.Fatal(err)
			}
		}
		if q := readQuota(*root); q == 0 {
			fmt.Println("no quota")
		} else {
			fmt.Println(humanSize(q))
		}
	case "invite":
		store := mustStore(*root)
		code, err := store.CreateInvite()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(code)
	case "user":
		if len(args) != 2 {
			os.Exit(2)
		}
		store := mustStore(*root)
		var password string
		fmt.Fscanln(os.Stdin, &password)
		if _, err := store.CreateUser(args[1], password); err != nil {
			log.Fatal(err)
		}
	default:
		flag.Usage()
		os.Exit(2)
	}
}

func mustStore(root string) *Store {
	if err := os.MkdirAll(filepath.Join(root, "repos"), 0o755); err != nil {
		log.Fatal(err)
	}
	store, err := openStore(filepath.Join(root, "basin.db"), false)
	if err != nil {
		log.Fatal(err)
	}
	return store
}

func serve(root string, args []string) {
	fs := flag.NewFlagSet("serve", flag.ExitOnError)
	listen := fs.String("listen", "127.0.0.1:3200", "address to listen on")
	host := fs.String("host", "localhost", "public hostname used in clone urls")
	open := fs.Bool("open", false, "allow registration without an invite")
	sshUser := fs.String("ssh-user", "git", "unix user that serves git over ssh, shown in clone urls")
	fs.Parse(args)
	store := mustStore(root)
	srv := &http.Server{
		Addr:              *listen,
		Handler:           newServer(store, root, *host, *open, *sshUser).handler(),
		ReadHeaderTimeout: 10 * time.Second,
	}
	log.Printf("basin listening on %s for %s", *listen, *host)
	log.Fatal(srv.ListenAndServe())
}