diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..89db521 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,11 @@ +package auth + +import ( + "gitlab.crans.org/nounous/ghostream/auth/ldap" +) + +// Options holds web package configuration +type Options struct { + Backend string + LDAP ldap.Options +} diff --git a/auth/ldap/ldap.go b/auth/ldap/ldap.go new file mode 100644 index 0000000..404d241 --- /dev/null +++ b/auth/ldap/ldap.go @@ -0,0 +1,7 @@ +package ldap + +// Options holds web package configuration +type Options struct { + URI string + UserDn string +} diff --git a/go.mod b/go.mod index 03778d2..de3a5f1 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.13 require ( github.com/prometheus/client_golang v1.7.1 github.com/spf13/viper v1.7.1 + honnef.co/go/tools v0.0.1-2019.2.3 ) diff --git a/go.sum b/go.sum index 30c1221..0b85708 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -296,6 +297,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -345,5 +347,6 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 392252b..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2020 Cr@ns - * Authors : Alexandre Iooss - * SPDX-License-Identifier: MIT - */ - -package config - -import ( - "log" - "strings" - - "github.com/spf13/viper" -) - -// Config holds app configuration -type Config struct { - AuthBackend string - LDAP struct { - URI string - UserDn string - } - Prometheus struct { - ListenAddress string - } - Site struct { - ListenAddress string - Name string - Hostname string - Favicon string - WidgetURL string - } -} - -// New configuration -func New() (*Config, error) { - // Load configuration from environnement variables - // Replace "." to "_" for nested structs - // e.g. GHOSTREAM_LDAP_URI will apply to Config.LDAP.URI - viper.SetEnvPrefix("ghostream") - replacer := strings.NewReplacer(".", "_") - viper.SetEnvKeyReplacer(replacer) - viper.AutomaticEnv() - - // Load configuration file if exists - viper.SetConfigName("ghostream") - viper.SetConfigType("yaml") - viper.AddConfigPath("$HOME/.ghostream") - viper.AddConfigPath("/etc/ghostream") - viper.AddConfigPath(".") - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - // Config file not found, ignore and use defaults - log.Print(err) - } else { - // Config file was found but another error was produced - log.Fatal(err) - } - } else { - // Config loaded - log.Printf("Using config file: %s", viper.ConfigFileUsed()) - } - - // Define configuration default values - viper.SetDefault("AuthBackend", "LDAP") - viper.SetDefault("LDAP.URI", "ldap://127.0.0.1:389") - viper.SetDefault("LDAP.UserDn", "cn=users,dc=example,dc=com") - viper.SetDefault("Prometheus.ListenAddress", "0.0.0.0:2112") - viper.SetDefault("Site.ListenAddress", "127.0.0.1:8080") - viper.SetDefault("Site.Name", "Ghostream") - viper.SetDefault("Site.Hostname", "localhost") - viper.SetDefault("Site.Favicon", "/favicon.ico") - - config := &Config{} - err := viper.Unmarshal(config) - return config, err -} diff --git a/internal/monitoring/monitoring.go b/internal/monitoring/monitoring.go index a1bc34a..8ec6626 100644 --- a/internal/monitoring/monitoring.go +++ b/internal/monitoring/monitoring.go @@ -7,9 +7,13 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" - "gitlab.crans.org/nounous/ghostream/internal/config" ) +// Options holds web package configuration +type Options struct { + ListenAddress string +} + var ( // ViewerServed is the total amount of viewer page served ViewerServed = promauto.NewCounter(prometheus.CounterOpts{ @@ -19,9 +23,9 @@ var ( ) // ServeHTTP server that expose prometheus metrics -func ServeHTTP(cfg *config.Config) { +func ServeHTTP(cfg *Options) { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) - log.Printf("Monitoring listening on http://%s/", cfg.Prometheus.ListenAddress) - log.Fatal(http.ListenAndServe(cfg.Prometheus.ListenAddress, mux)) + log.Printf("Monitoring HTTP server listening on %s", cfg.ListenAddress) + log.Fatal(http.ListenAndServe(cfg.ListenAddress, mux)) } diff --git a/main.go b/main.go index c982fc2..0d0554b 100644 --- a/main.go +++ b/main.go @@ -2,27 +2,73 @@ package main import ( "log" + "strings" - "gitlab.crans.org/nounous/ghostream/internal/config" + "github.com/spf13/viper" + "gitlab.crans.org/nounous/ghostream/auth" "gitlab.crans.org/nounous/ghostream/internal/monitoring" "gitlab.crans.org/nounous/ghostream/web" ) +func loadConfiguration() { + // Load configuration from environnement variables + // Replace "." to "_" for nested structs + // e.g. GHOSTREAM_LDAP_URI will apply to Config.LDAP.URI + viper.SetEnvPrefix("ghostream") + replacer := strings.NewReplacer(".", "_") + viper.SetEnvKeyReplacer(replacer) + viper.AutomaticEnv() + + // Load configuration file if exists + viper.SetConfigName("ghostream") + viper.SetConfigType("yaml") + viper.AddConfigPath("$HOME/.ghostream") + viper.AddConfigPath("/etc/ghostream") + viper.AddConfigPath(".") + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found, ignore and use defaults + log.Print(err) + } else { + // Config file was found but another error was produced + log.Fatal(err) + } + } else { + // Config loaded + log.Printf("Using config file: %s", viper.ConfigFileUsed()) + } + + // Define configuration default values + viper.SetDefault("Auth.Backend", "LDAP") + viper.SetDefault("Auth.LDAP.URI", "ldap://127.0.0.1:389") + viper.SetDefault("Auth.LDAP.UserDn", "cn=users,dc=example,dc=com") + viper.SetDefault("Monitoring.ListenAddress", ":2112") + viper.SetDefault("Web.ListenAddress", "127.0.0.1:8080") + viper.SetDefault("Web.Name", "Ghostream") + viper.SetDefault("Web.Hostname", "localhost") + viper.SetDefault("Web.Favicon", "/favicon.ico") +} + func main() { // Load configuration - cfg, err := config.New() - if err != nil { - log.Fatal(err) + loadConfiguration() + cfg := struct { + Auth auth.Options + Monitoring monitoring.Options + Web web.Options + }{} + if err := viper.Unmarshal(&cfg); err != nil { + log.Fatalln("Failed to load settings", err) } // Start web server routine go func() { - web.ServeHTTP(cfg) + web.ServeHTTP(&cfg.Web) }() // Start monitoring server routine go func() { - monitoring.ServeHTTP(cfg) + monitoring.ServeHTTP(&cfg.Monitoring) }() // Wait for routines diff --git a/web/template/base.tmpl b/web/template/base.tmpl index 08054d7..e709b20 100644 --- a/web/template/base.tmpl +++ b/web/template/base.tmpl @@ -3,7 +3,7 @@ - {{if .Path}}{{.Path}} - {{end}}{{.Cfg.Site.Name}} + {{if .Path}}{{.Path}} - {{end}}{{.Cfg.Name}} diff --git a/web/template/index.tmpl b/web/template/index.tmpl index c2c12bf..67ea780 100644 --- a/web/template/index.tmpl +++ b/web/template/index.tmpl @@ -1,8 +1,8 @@ {{define "index"}}
-

{{.Cfg.Site.Name}}

+

{{.Cfg.Name}}

- {{.Cfg.Site.Name}} est un service maintenu par le + {{.Cfg.Name}} est un service maintenu par le Crans permettant de diffuser un contenu vidéo. Il a pour but d'être utilisé pour diffuser des séminaires ou évènements. @@ -21,7 +21,7 @@

  • Serveur : - rtmps://{{.Cfg.Site.Hostname}}:1935/stream, + rtmps://{{.Cfg.Hostname}}:1935/stream,
  • Clé de stream : @@ -41,7 +41,7 @@

    Votre stream sera alors disponible sur - https://{{.Cfg.Site.Hostname}}/IDENTIFIANT. + https://{{.Cfg.Hostname}}/IDENTIFIANT.

    Avec FFmpeg

    @@ -49,7 +49,7 @@ ffmpeg -re -i mavideo.webm -vcodec libx264 -vprofile baseline -acodec aac -strict -2 -f flv - rtmps://{{.Cfg.Site.Hostname}}:1935/stream/IDENTIFIANT?pass=MOT_DE_PASSE + rtmps://{{.Cfg.Hostname}}:1935/stream/IDENTIFIANT?pass=MOT_DE_PASSE

    diff --git a/web/template/viewer.tmpl b/web/template/viewer.tmpl index 1e75884..0564e03 100644 --- a/web/template/viewer.tmpl +++ b/web/template/viewer.tmpl @@ -10,7 +10,7 @@ - rtmps://{{.Cfg.Site.Hostname}}:1935/play/{{.Path}} + rtmps://{{.Cfg.Hostname}}:1935/play/{{.Path}} »

@@ -43,7 +43,7 @@ player = OvenPlayer.create("player", { expandFullScreenUI: true, sources: [ { - "file": "wss://{{.Cfg.Site.Hostname}}/play/{{.Path}}", + "file": "wss://{{.Cfg.Hostname}}/play/{{.Path}}", "type": "webrtc", "label": "WebRTC Source" } diff --git a/web/web.go b/web/web.go index 3024d02..38d99ac 100644 --- a/web/web.go +++ b/web/web.go @@ -6,19 +6,27 @@ import ( "net/http" "os" - "gitlab.crans.org/nounous/ghostream/internal/config" "gitlab.crans.org/nounous/ghostream/internal/monitoring" ) +// Options holds web package configuration +type Options struct { + ListenAddress string + Name string + Hostname string + Favicon string + WidgetURL string +} + // Preload templates var templates = template.Must(template.ParseGlob("web/template/*.tmpl")) // Handle site index and viewer pages -func viewerHandler(w http.ResponseWriter, r *http.Request, cfg *config.Config) { +func viewerHandler(w http.ResponseWriter, r *http.Request, cfg *Options) { // Data for template data := struct { Path string - Cfg *config.Config + Cfg *Options }{Path: r.URL.Path[1:], Cfg: cfg} // FIXME validation on path: https://golang.org/doc/articles/wiki/#tmp_11 @@ -35,7 +43,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, cfg *config.Config) { } // Auth incoming stream -func streamAuthHandler(w http.ResponseWriter, r *http.Request, cfg *config.Config) { +func streamAuthHandler(w http.ResponseWriter, r *http.Request, cfg *Options) { // FIXME POST request only with "name" and "pass" // if name or pass missing => 400 Malformed request // else login in against LDAP or static users @@ -44,7 +52,7 @@ func streamAuthHandler(w http.ResponseWriter, r *http.Request, cfg *config.Confi // Handle static files // We do not use http.FileServer as we do not want directory listing -func staticHandler(w http.ResponseWriter, r *http.Request, cfg *config.Config) { +func staticHandler(w http.ResponseWriter, r *http.Request, cfg *Options) { path := "./web/" + r.URL.Path if f, err := os.Stat(path); err == nil && !f.IsDir() { http.ServeFile(w, r, path) @@ -54,19 +62,19 @@ func staticHandler(w http.ResponseWriter, r *http.Request, cfg *config.Config) { } // Closure to pass configuration -func makeHandler(fn func(http.ResponseWriter, *http.Request, *config.Config), cfg *config.Config) http.HandlerFunc { +func makeHandler(fn func(http.ResponseWriter, *http.Request, *Options), cfg *Options) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fn(w, r, cfg) } } // ServeHTTP server -func ServeHTTP(cfg *config.Config) { +func ServeHTTP(cfg *Options) { // Set up HTTP router and server mux := http.NewServeMux() mux.HandleFunc("/", makeHandler(viewerHandler, cfg)) mux.HandleFunc("/rtmp/auth", makeHandler(streamAuthHandler, cfg)) mux.HandleFunc("/static/", makeHandler(staticHandler, cfg)) - log.Printf("Listening on http://%s/", cfg.Site.ListenAddress) - log.Fatal(http.ListenAndServe(cfg.Site.ListenAddress, mux)) + log.Printf("HTTP server listening on %s", cfg.ListenAddress) + log.Fatal(http.ListenAndServe(cfg.ListenAddress, mux)) }