package main import ( "log" "net/http" "os" "path/filepath" "runtime" "runtime/pprof" "github.com/spf13/cobra" "goji.io" "goji.io/pat" ) // cmdRoot is the base command when no other command has been specified. var cmdRoot = &cobra.Command{ Use: "rest-server", Short: "Run a REST server for use with restic", SilenceErrors: true, SilenceUsage: true, RunE: runRoot, } var config = struct { path string listen string tls bool cpuprofile string debug bool }{} func init() { flags := cmdRoot.Flags() flags.StringVar(&config.path, "path", "/tmp/restic", "data directory") flags.StringVar(&config.listen, "listen", ":8000", "listen address") flags.BoolVar(&config.tls, "tls", false, "turn on TLS support") flags.StringVar(&config.cpuprofile, "cpuprofile", "", "write CPU profile to file") flags.BoolVar(&config.debug, "debug", false, "output debug messages") } func debugHandler(next http.Handler) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s", r.Method, r.URL) next.ServeHTTP(w, r) }) } func setupMux() *goji.Mux { mux := goji.NewMux() if config.debug { mux.Use(debugHandler) } mux.HandleFunc(pat.Head("/config"), CheckConfig) mux.HandleFunc(pat.Head("/:repo/config"), CheckConfig) mux.HandleFunc(pat.Get("/config"), GetConfig) mux.HandleFunc(pat.Get("/:repo/config"), GetConfig) mux.HandleFunc(pat.Post("/config"), SaveConfig) mux.HandleFunc(pat.Post("/:repo/config"), SaveConfig) mux.HandleFunc(pat.Delete("/config"), DeleteConfig) mux.HandleFunc(pat.Delete("/:repo/config"), DeleteConfig) mux.HandleFunc(pat.Get("/:type/"), ListBlobs) mux.HandleFunc(pat.Get("/:repo/:type/"), ListBlobs) mux.HandleFunc(pat.Head("/:type/:name"), CheckBlob) mux.HandleFunc(pat.Head("/:repo/:type/:name"), CheckBlob) mux.HandleFunc(pat.Get("/:type/:name"), GetBlob) mux.HandleFunc(pat.Get("/:repo/:type/:name"), GetBlob) mux.HandleFunc(pat.Post("/:type/:name"), SaveBlob) mux.HandleFunc(pat.Post("/:repo/:type/:name"), SaveBlob) mux.HandleFunc(pat.Delete("/:type/:name"), DeleteBlob) mux.HandleFunc(pat.Delete("/:repo/:type/:name"), DeleteBlob) mux.HandleFunc(pat.Post("/"), CreateRepo) mux.HandleFunc(pat.Post("/:repo"), CreateRepo) mux.HandleFunc(pat.Post("/:repo/"), CreateRepo) return mux } var version = "manually" func runRoot(cmd *cobra.Command, args []string) error { log.SetFlags(0) log.Printf("rest-server %s compiled with %v on %v/%v\n", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) log.Printf("Data directory: %s", config.path) if config.cpuprofile != "" { f, err := os.Create(config.cpuprofile) if err != nil { log.Fatal(err) } if err := pprof.StartCPUProfile(f); err != nil { log.Fatal(err) } log.Println("CPU profiling enabled") defer pprof.StopCPUProfile() } mux := setupMux() var handler http.Handler htpasswdFile, err := NewHtpasswdFromFile(filepath.Join(config.path, ".htpasswd")) if err != nil { handler = mux log.Println("Authentication disabled") } else { handler = AuthHandler(htpasswdFile, mux) log.Println("Authentication enabled") } if !config.tls { log.Printf("Starting server on %s\n", config.listen) err = http.ListenAndServe(config.listen, handler) } else { privateKey := filepath.Join(config.path, "private_key") publicKey := filepath.Join(config.path, "public_key") log.Println("TLS enabled") log.Printf("Private key: %s", privateKey) log.Printf("Public key: %s", publicKey) log.Printf("Starting server on %s\n", config.listen) err = http.ListenAndServeTLS(config.listen, publicKey, privateKey, handler) } if err != nil { log.Fatal(err) } return nil } func main() { if err := cmdRoot.Execute(); err != nil { log.Fatalf("error: %v", err) } }