Restructure configuration

This commit is contained in:
Alexandre Iooss 2020-09-22 11:42:57 +02:00
parent c799a5b613
commit 5ac336393b
11 changed files with 107 additions and 104 deletions

11
auth/auth.go Normal file
View File

@ -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
}

7
auth/ldap/ldap.go Normal file
View File

@ -0,0 +1,7 @@
package ldap
// Options holds web package configuration
type Options struct {
URI string
UserDn string
}

1
go.mod
View File

@ -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
)

3
go.sum
View File

@ -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=

View File

@ -1,77 +0,0 @@
/*
* Copyright (C) 2020 Cr@ns <roots@crans.org>
* Authors : Alexandre Iooss <erdnaxe@crans.org>
* 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
}

View File

@ -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))
}

58
main.go
View File

@ -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

View File

@ -3,7 +3,7 @@
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>{{if .Path}}{{.Path}} - {{end}}{{.Cfg.Site.Name}}</title>
<title>{{if .Path}}{{.Path}} - {{end}}{{.Cfg.Name}}</title>
<link rel="stylesheet" href="static/style.css">
<link rel="shortcut icon" href="static/favicon.ico">
</head>

View File

@ -1,8 +1,8 @@
{{define "index"}}
<div style="max-width:720px;margin:0 auto; padding: 1rem">
<h1>{{.Cfg.Site.Name}}</h1>
<h1>{{.Cfg.Name}}</h1>
<p>
{{.Cfg.Site.Name}} est un service maintenu par le
{{.Cfg.Name}} est un service maintenu par le
<a href="https://crans.org/">Crans</a> 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 @@
<ul>
<li>
<b>Serveur :</b>
<code>rtmps://{{.Cfg.Site.Hostname}}:1935/stream</code>,
<code>rtmps://{{.Cfg.Hostname}}:1935/stream</code>,
</li>
<li>
<b>Clé de stream :</b>
@ -41,7 +41,7 @@
<p>
Votre stream sera alors disponible sur
<code>https://{{.Cfg.Site.Hostname}}/IDENTIFIANT</code>.
<code>https://{{.Cfg.Hostname}}/IDENTIFIANT</code>.
</p>
<h3>Avec FFmpeg</h3>
@ -49,7 +49,7 @@
<code>
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
</code>
</p>

View File

@ -10,7 +10,7 @@
<path fill-rule="evenodd" d="M1.5 13A1.5 1.5 0 0 0 3 14.5h8a1.5 1.5 0 0 0 1.5-1.5V9a.5.5 0 0 0-1 0v4a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 0 0-1H3A1.5 1.5 0 0 0 1.5 5v8zm7-11a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-1 0V2.5H9a.5.5 0 0 1-.5-.5z"/>
<path fill-rule="evenodd" d="M14.354 1.646a.5.5 0 0 1 0 .708l-8 8a.5.5 0 0 1-.708-.708l8-8a.5.5 0 0 1 .708 0z"/>
</svg>
<code>rtmps://{{.Cfg.Site.Hostname}}:1935/play/{{.Path}}</code>
<code>rtmps://{{.Cfg.Hostname}}:1935/play/{{.Path}}</code>
<a href="#" id="chatToggle" title="Cacher/Afficher le chat">»</a>
</p>
</div>
@ -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"
}

View File

@ -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))
}