clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / cmd / tinygit / main.go
main.go 2.4K
1// Command tinygit serves signed repository metadata and native Git smart HTTP.2package main34import (5 "context"6 "errors"7 "flag"8 "fmt"9 "io"10 "net"11 "net/http"12 "os"13 "os/exec"14 "os/signal"15 "syscall"16 "time"1718 "github.com/FelineStateMachine/tinyrelay/tinygit"19)2021func main() {22 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)23 defer stop()24 if err := run(ctx, os.Args[1:], os.Stdout, os.Stderr); err != nil {25 fmt.Fprintln(os.Stderr, "tinygit:", err)26 os.Exit(1)27 }28}2930func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {31 flags := flag.NewFlagSet("tinygit", flag.ContinueOnError)32 flags.SetOutput(stderr)33 listen := flags.String("listen", "127.0.0.1:8082", "HTTP listen address (put TLS and request limits in front for public hosting)")34 data := flags.String("data", "./tinygit-data", "dedicated data directory for events.db and bare Git repositories")35 owner := flags.String("owner", "", "required repository owner's lowercase hex Nostr public key")36 publicURL := flags.String("public-url", "", "canonical public HTTP(S) URL")37 if err := flags.Parse(args); err != nil {38 if errors.Is(err, flag.ErrHelp) {39 return nil40 }41 return err42 }43 if flags.NArg() != 0 {44 return errors.New("unexpected positional arguments")45 }46 if _, err := exec.LookPath("git"); err != nil {47 return fmt.Errorf("native Git is required: %w", err)48 }49 handler, err := tinygit.OpenServer(ctx, tinygit.ServerConfig{DataDir: *data, Owner: *owner, PublicURL: *publicURL})50 if err != nil {51 return err52 }53 defer handler.Close()54 listener, err := net.Listen("tcp", *listen)55 if err != nil {56 return err57 }58 defer listener.Close()59 server := &http.Server{60 Handler: handler,61 ReadHeaderTimeout: 10 * time.Second,62 IdleTimeout: 60 * time.Second,63 MaxHeaderBytes: 1 << 20,64 BaseContext: func(net.Listener) context.Context { return ctx },65 }66 done := make(chan error, 1)67 go func() { done <- server.Serve(listener) }()68 fmt.Fprintln(stdout, "tinygit listening on http://"+listener.Addr().String())69 select {70 case err := <-done:71 if errors.Is(err, http.ErrServerClosed) {72 return nil73 }74 return err75 case <-ctx.Done():76 shutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second)77 defer cancel()78 err := server.Shutdown(shutdown)79 if err != nil {80 _ = server.Close()81 }82 serveErr := <-done83 if errors.Is(serveErr, http.ErrServerClosed) {84 serveErr = nil85 }86 return errors.Join(err, serveErr)87 }88}