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-23 01:21:39 +00:00
serveCmd . Flags ( ) . String ( "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-23 01:21:39 +00:00
viper . SetDefault ( "serve.app-root" , "http://localhost:8191/" )
serveCmd . Flags ( ) . String ( "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-23 01:21:39 +00:00
viper . SetDefault ( "serve.static-root" , "/static/" )
serveCmd . Flags ( ) . StringP ( "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-23 01:21:39 +00:00
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." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.direct-only" , serveCmd . Flags ( ) . Lookup ( "direct-only" ) )
2021-03-23 01:21:39 +00:00
viper . SetDefault ( "serve.direct-only" , false )
2021-03-21 18:52:22 +00:00
2021-03-23 01:21:39 +00:00
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." )
2021-03-23 00:45:51 +00:00
viper . BindPFlag ( "serve.cheddar.path" , serveCmd . Flags ( ) . Lookup ( "cheddar-path" ) )
2021-03-23 01:21:39 +00:00
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." )
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
2021-03-23 01:21:39 +00:00
serveCmd . Flags ( ) . String ( "auth-token" , "" , "If non-empty, this auth token will be required as the Basic Auth password." )
2021-03-23 00:46:33 +00:00
viper . BindPFlag ( "serve.auth.token" , serveCmd . Flags ( ) . Lookup ( "auth-token" ) )
2021-03-23 01:21:39 +00:00
serveCmd . Flags ( ) . String ( "auth-realm" , "fup" , "Will be used as the realm for Basic Auth." )
2021-03-23 00:46:33 +00:00
viper . BindPFlag ( "serve.auth.realm" , serveCmd . Flags ( ) . Lookup ( "auth-realm" ) )
2021-03-23 01:21:39 +00:00
viper . SetDefault ( "serve.auth.realm" , "fup" )
2021-03-20 19:53:43 +00:00
}
var (
serveCmd = & cobra . Command {
Use : "serve" ,
Short : "Serve HTTP" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-03-23 01:21:39 +00:00
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" ) )
2021-03-21 18:04:37 +00:00
}
2021-03-23 01:21:39 +00:00
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" ) )
2021-03-21 18:04:37 +00:00
}
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 ) ,
2021-03-23 01:21:39 +00:00
AppRoot : viper . GetString ( "serve.app-root" ) ,
StorageURL : bucketURL ( ) ,
RedirectToBlobstore : ! viper . GetBool ( "serve.direct-only" ) ,
AuthMiddleware : fuphttp . TokenAuthMiddleware ( viper . GetString ( "serve.auth.token" ) , viper . GetString ( "serve.auth.realm" ) ) ,
2021-03-20 19:53:43 +00:00
}
2021-03-26 21:44:13 +00:00
if highlighter != nil {
cfg . Highlighter = highlighter
}
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-23 01:21:39 +00:00
log . Printf ( "Serving on %s" , viper . GetString ( "serve.listen" ) )
if viper . GetString ( "serve.listen" ) == "systemd" {
2021-03-22 02:41:59 +00:00
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-23 01:21:39 +00:00
return http . ListenAndServe ( viper . GetString ( "serve.listen" ) , nil )
2021-03-20 19:53:43 +00:00
} ,
}
)
2021-03-21 18:52:22 +00:00
func serveCheddar ( ctx context . Context ) ( * minicheddar . Cheddar , error ) {
2021-03-23 01:21:39 +00:00
if serveCheddarAddr := viper . GetString ( "serve.cheddar.addr" ) ; serveCheddarAddr != "" {
2021-03-21 18:52:22 +00:00
return minicheddar . Remote ( serveCheddarAddr ) , nil
}
2021-03-23 01:21:39 +00:00
cpath , err := exec . LookPath ( viper . GetString ( "serve.cheddar.path" ) )
2021-03-21 18:52:22 +00:00
if err != nil {
2021-03-23 01:21:39 +00:00
log . Printf ( "couldn't find cheddar at %q; disabling syntax highlighting" , viper . GetString ( "serve.cheddar.path" ) )
2021-03-21 18:52:22 +00:00
return nil , nil
}
return minicheddar . Spawn ( ctx , cpath )
}