depot/web/fup/cmd/serve.go
Luke Granger-Brown affe252f73 fup: fix config file handling
Whoops! Before, config files were being read but all the contents were basically
being discarded.

Now, we both load and actually use the config file, leading to a much more positive
experience for everyone involved :)
2021-03-23 01:21:39 +00:00

119 lines
4.8 KiB
Go

// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
//
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"fmt"
"log"
"net/http"
"os/exec"
"strings"
"github.com/coreos/go-systemd/v22/activation"
"github.com/google/safehtml"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"hg.lukegb.com/lukegb/depot/web/fup/fuphttp"
"hg.lukegb.com/lukegb/depot/web/fup/fupstatic"
"hg.lukegb.com/lukegb/depot/web/fup/minicheddar"
)
func init() {
rootCmd.AddCommand(serveCmd)
serveCmd.Flags().String("root", "http://localhost:8191/", "Application root address.")
viper.BindPFlag("serve.app-root", serveCmd.Flags().Lookup("root"))
viper.SetDefault("serve.app-root", "http://localhost:8191/")
serveCmd.Flags().String("static-root", "/static/", "Root address from which static assets should be referenced.")
viper.BindPFlag("serve.static-root", serveCmd.Flags().Lookup("static-root"))
viper.SetDefault("serve.static-root", "/static/")
serveCmd.Flags().StringP("listen", "l", ":8191", "Bind address for HTTP server.")
viper.BindPFlag("serve.listen", serveCmd.Flags().Lookup("listen"))
viper.SetDefault("serve.listen", ":8191")
serveCmd.Flags().Bool("direct-only", false, "If set, all file serving will be proxied, even if the backend supports signed URLs.")
viper.BindPFlag("serve.direct-only", serveCmd.Flags().Lookup("direct-only"))
viper.SetDefault("serve.direct-only", false)
serveCmd.Flags().String("cheddar-path", "cheddar", "Path to 'cheddar' binary to use for syntax highlighting. If it cannot be found, syntax highlighting and markdown rendering will be disabled.")
viper.BindPFlag("serve.cheddar.path", serveCmd.Flags().Lookup("cheddar-path"))
viper.SetDefault("serve.cheddar.path", "cheddar")
serveCmd.Flags().String("cheddar-address", "", "If non-empty, will be used instead of attempting to spawn a copy of cheddar.")
viper.BindPFlag("serve.cheddar.address", serveCmd.Flags().Lookup("cheddar-address"))
serveCmd.Flags().String("auth-token", "", "If non-empty, this auth token will be required as the Basic Auth password.")
viper.BindPFlag("serve.auth.token", serveCmd.Flags().Lookup("auth-token"))
serveCmd.Flags().String("auth-realm", "fup", "Will be used as the realm for Basic Auth.")
viper.BindPFlag("serve.auth.realm", serveCmd.Flags().Lookup("auth-realm"))
viper.SetDefault("serve.auth.realm", "fup")
}
var (
serveCmd = &cobra.Command{
Use: "serve",
Short: "Serve HTTP",
RunE: func(cmd *cobra.Command, args []string) error {
if !strings.HasSuffix(viper.GetString("serve.app-root"), "/") {
return fmt.Errorf("--root flag (serve.app-root) should end in / (value is %q)", viper.GetString("serve.app-root"))
}
if !strings.HasSuffix(viper.GetString("serve.static-root"), "/") {
return fmt.Errorf("--static-root flag (serve.static-root) should end in / (value is %q)", viper.GetString("serve.static-root"))
}
ctx := context.Background()
highlighter, err := serveCheddar(ctx)
if err != nil {
return fmt.Errorf("spawning cheddar syntax highlighter: %v", err)
}
cfg := &fuphttp.Config{
Templates: fupstatic.Templates,
Static: fupstatic.Static,
StaticRoot: safehtml.TrustedResourceURLFromFlag(cmd.Flag("static-root").Value),
AppRoot: viper.GetString("serve.app-root"),
StorageURL: bucketURL(),
RedirectToBlobstore: !viper.GetBool("serve.direct-only"),
Highlighter: highlighter,
AuthMiddleware: fuphttp.TokenAuthMiddleware(viper.GetString("serve.auth.token"), viper.GetString("serve.auth.realm")),
}
a, err := fuphttp.New(ctx, cfg)
if err != nil {
return fmt.Errorf("constructing application: %w", err)
}
http.Handle("/", a.Handler())
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(fupstatic.Static))))
log.Printf("Serving on %s", viper.GetString("serve.listen"))
if viper.GetString("serve.listen") == "systemd" {
listeners, err := activation.Listeners()
if err != nil {
return fmt.Errorf("getting systemd socket-activated listeners: %v", err)
}
if len(listeners) != 1 {
return fmt.Errorf("unexpected systemd socket activation fds: got %d; want 1", len(listeners))
}
return http.Serve(listeners[0], nil)
}
return http.ListenAndServe(viper.GetString("serve.listen"), nil)
},
}
)
func serveCheddar(ctx context.Context) (*minicheddar.Cheddar, error) {
if serveCheddarAddr := viper.GetString("serve.cheddar.addr"); serveCheddarAddr != "" {
return minicheddar.Remote(serveCheddarAddr), nil
}
cpath, err := exec.LookPath(viper.GetString("serve.cheddar.path"))
if err != nil {
log.Printf("couldn't find cheddar at %q; disabling syntax highlighting", viper.GetString("serve.cheddar.path"))
return nil, nil
}
return minicheddar.Spawn(ctx, cpath)
}