2021-03-20 20:40:40 +00:00
// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
//
// SPDX-License-Identifier: Apache-2.0
2021-03-20 19:53:43 +00:00
package cmd
import (
"context"
"fmt"
2021-03-21 03:04:38 +00:00
"log"
2021-03-20 19:53:43 +00:00
"net/http"
2021-03-21 18:52:22 +00:00
"os/exec"
2021-03-21 18:04:37 +00:00
"strings"
2021-03-20 19:53:43 +00:00
2021-03-22 02:41:59 +00:00
"github.com/coreos/go-systemd/v22/activation"
2021-03-21 03:04:38 +00:00
"github.com/google/safehtml"
2021-03-20 19:53:43 +00:00
"github.com/spf13/cobra"
2021-03-23 00:45:51 +00:00
"github.com/spf13/viper"
2021-03-20 19:53:43 +00:00
"hg.lukegb.com/lukegb/depot/web/fup/fuphttp"
"hg.lukegb.com/lukegb/depot/web/fup/fupstatic"
2021-03-21 18:52:22 +00:00
"hg.lukegb.com/lukegb/depot/web/fup/minicheddar"
2021-03-20 19:53:43 +00:00
)
func init ( ) {
rootCmd . AddCommand ( serveCmd )
2021-03-21 18:04:37 +00:00
serveCmd . Flags ( ) . StringVar ( & serveRoot , "root" , "http://localhost:8191/" , "Application root address." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.app-root" , serveCmd . Flags ( ) . Lookup ( "root" ) )
2021-03-21 18:04:37 +00:00
serveCmd . Flags ( ) . StringVar ( & serveStaticRoot , "static-root" , "/static/" , "Root address from which static assets should be referenced." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.static-root" , serveCmd . Flags ( ) . Lookup ( "static-root" ) )
2021-03-20 19:53:43 +00:00
serveCmd . Flags ( ) . StringVarP ( & serveBind , "listen" , "l" , ":8191" , "Bind address for HTTP server." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.listen" , serveCmd . Flags ( ) . Lookup ( "listen" ) )
2021-03-21 03:04:38 +00:00
serveCmd . Flags ( ) . BoolVar ( & serveDirectOnly , "direct-only" , false , "If set, all file serving will be proxied, even if the backend supports signed URLs." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.direct-only" , serveCmd . Flags ( ) . Lookup ( "direct-only" ) )
2021-03-21 18:52:22 +00:00
serveCmd . Flags ( ) . StringVar ( & serveCheddarPath , "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." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.cheddar.path" , serveCmd . Flags ( ) . Lookup ( "cheddar-path" ) )
2021-03-21 18:52:22 +00:00
serveCmd . Flags ( ) . StringVar ( & serveCheddarAddr , "cheddar-address" , "" , "If non-empty, will be used instead of attempting to spawn a copy of cheddar." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.cheddar.address" , serveCmd . Flags ( ) . Lookup ( "cheddar-address" ) )
2021-03-23 00:46:33 +00:00
serveCmd . Flags ( ) . StringVar ( & serveAuthToken , "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 ( ) . StringVar ( & serveAuthRealm , "auth-realm" , "fup" , "Will be used as the realm for Basic Auth." )
viper . BindPFlag ( "serve.auth.realm" , serveCmd . Flags ( ) . Lookup ( "auth-realm" ) )
2021-03-20 19:53:43 +00:00
}
var (
2021-03-21 18:52:22 +00:00
serveBind string
serveRoot string
serveStaticRoot string
serveDirectOnly bool
serveCheddarPath string
serveCheddarAddr string
2021-03-23 00:46:33 +00:00
serveAuthToken string
serveAuthRealm string
2021-03-20 19:53:43 +00:00
serveCmd = & cobra . Command {
Use : "serve" ,
Short : "Serve HTTP" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-03-21 18:04:37 +00:00
if ! strings . HasSuffix ( serveRoot , "/" ) {
return fmt . Errorf ( "--root flag should end in / (value is %q)" , serveRoot )
}
if ! strings . HasSuffix ( serveStaticRoot , "/" ) {
return fmt . Errorf ( "--static-root flag should end in / (value is %q)" , serveStaticRoot )
}
2021-03-20 19:53:43 +00:00
ctx := context . Background ( )
2021-03-21 18:52:22 +00:00
highlighter , err := serveCheddar ( ctx )
if err != nil {
return fmt . Errorf ( "spawning cheddar syntax highlighter: %v" , err )
}
2021-03-20 19:53:43 +00:00
cfg := & fuphttp . Config {
2021-03-21 03:04:38 +00:00
Templates : fupstatic . Templates ,
Static : fupstatic . Static ,
2021-03-21 18:04:37 +00:00
StaticRoot : safehtml . TrustedResourceURLFromFlag ( cmd . Flag ( "static-root" ) . Value ) ,
AppRoot : serveRoot ,
2021-03-21 03:04:38 +00:00
StorageURL : bucketURL ,
RedirectToBlobstore : ! serveDirectOnly ,
2021-03-21 18:52:22 +00:00
Highlighter : highlighter ,
2021-03-23 00:46:33 +00:00
AuthMiddleware : fuphttp . TokenAuthMiddleware ( serveAuthToken , serveAuthRealm ) ,
2021-03-20 19:53:43 +00:00
}
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 ) ) ) )
2021-03-21 03:04:38 +00:00
log . Printf ( "Serving on %s" , serveBind )
2021-03-22 02:41:59 +00:00
if serveBind == "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 )
}
2021-03-20 19:53:43 +00:00
return http . ListenAndServe ( serveBind , nil )
} ,
}
)
2021-03-21 18:52:22 +00:00
func serveCheddar ( ctx context . Context ) ( * minicheddar . Cheddar , error ) {
if serveCheddarAddr != "" {
return minicheddar . Remote ( serveCheddarAddr ) , nil
}
cpath , err := exec . LookPath ( serveCheddarPath )
if err != nil {
log . Printf ( "couldn't find cheddar at %q; disabling syntax highlighting" , serveCheddarPath )
return nil , nil
}
return minicheddar . Spawn ( ctx , cpath )
}