From 5756dcad5c769361febba68f05887a0da9fbb3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 17:24:00 +0200 Subject: [PATCH 01/32] add proxy (WIP). --- internal/proxy/manager.go | 256 +++++++++++++++++++++++++++++++++++++ internal/server/manager.go | 4 +- internal/types/proxy.go | 8 ++ neko.go | 16 ++- 4 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 internal/proxy/manager.go create mode 100644 internal/types/proxy.go diff --git a/internal/proxy/manager.go b/internal/proxy/manager.go new file mode 100644 index 0000000..6bb3d30 --- /dev/null +++ b/internal/proxy/manager.go @@ -0,0 +1,256 @@ +package proxy + +import ( + "context" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "path" + "strings" + "sync" + + dockerTypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + dockerClient "github.com/docker/docker/client" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + + "github.com/m1k1o/neko-rooms/internal/config" +) + +type ProxyManagerCtx struct { + logger zerolog.Logger + mu sync.RWMutex + ctx context.Context + cancel func() + prefix string + client *dockerClient.Client + config *config.Room + handlers map[string]http.Handler +} + +func New(client *dockerClient.Client, config *config.Room) *ProxyManagerCtx { + logger := log.With().Str("module", "proxy").Logger() + + prefix := config.PathPrefix + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + + return &ProxyManagerCtx{ + logger: logger, + prefix: prefix, + client: client, + config: config, + handlers: map[string]http.Handler{}, + } +} + +func (p *ProxyManagerCtx) Start() { + p.ctx, p.cancel = context.WithCancel(context.Background()) + + go func() { + err := p.Refresh() + if err != nil { + p.logger.Err(err).Msg("unable to refresh containers") + } + + msgs, errs := p.client.Events(p.ctx, dockerTypes.EventsOptions{ + Filters: filters.NewArgs( + filters.Arg("type", "container"), + filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.config.InstanceName)), + filters.Arg("event", "create"), + filters.Arg("event", "start"), + filters.Arg("event", "stop"), + filters.Arg("event", "destroy"), + ), + }) + + for { + select { + case err := <-errs: + p.logger.Info().Interface("err", err).Msg("eee") + case msg := <-msgs: + host := msg.ID[:12] + name, port, ok := p.parseLabels(msg.Actor.Attributes) + if !ok { + break + } + + switch msg.Action { + case "create": + p.logger.Info(). + Str("id", msg.ID). + Str("name", name). + Str("host", host). + Msg("container created") + + case "start": + p.Add(name, host+":"+port) + + p.logger.Info(). + Str("id", msg.ID). + Str("name", name). + Str("host", host). + Msg("container started") + + case "stop": + p.Remove(name) + + p.logger.Info(). + Str("id", msg.ID). + Str("name", name). + Str("host", host). + Msg("container stopped") + + case "destroy": + p.logger.Info(). + Str("id", msg.ID). + Str("name", name). + Str("host", host). + Msg("container destroyed") + } + } + } + }() +} + +func (p *ProxyManagerCtx) Shutdown() error { + p.Clear() + + p.cancel() + return p.ctx.Err() +} + +func (p *ProxyManagerCtx) refresh() error { + containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{ + All: true, + Filters: filters.NewArgs( + filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.config.InstanceName)), + ), + }) + + if err != nil { + return err + } + + p.clear() + + for _, cont := range containers { + name, port, ok := p.parseLabels(cont.Labels) + if ok { + host := cont.ID[:12] + p.add(name, host+":"+port) + } + } + + return nil +} + +func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (name string, port string, ok bool) { + name, ok = labels["m1k1o.neko_rooms.name"] + if !ok { + return + } + + // TODO: Do not use trafik for this. + port, ok = labels["traefik.http.services.neko-rooms-"+name+"-frontend.loadbalancer.server.port"] + return +} + +func (p *ProxyManagerCtx) add(name, host string) { + p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "http", + Host: host, + }) + + p.logger.Debug().Str("name", name).Str("host", host).Msg("add handler") +} + +func (p *ProxyManagerCtx) remove(name string) { + delete(p.handlers, name) + p.logger.Debug().Str("name", name).Msg("remove handler") +} + +func (p *ProxyManagerCtx) clear() { + p.handlers = map[string]http.Handler{} + p.logger.Debug().Msg("clear handlers") +} + +func (p *ProxyManagerCtx) Refresh() error { + p.mu.Lock() + defer p.mu.Unlock() + + return p.refresh() +} + +func (p *ProxyManagerCtx) Add(name, host string) { + p.mu.Lock() + defer p.mu.Unlock() + + p.add(name, host) +} + +func (p *ProxyManagerCtx) Remove(name string) { + p.mu.Lock() + defer p.mu.Unlock() + + p.remove(name) +} + +func (p *ProxyManagerCtx) Clear() { + p.mu.Lock() + defer p.mu.Unlock() + + p.clear() +} + +func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // only if has prefix + if !strings.HasPrefix(r.URL.Path, p.prefix) { + http.NotFound(w, r) + return + } + + // remove prefix and leading / + roomPath := strings.TrimPrefix(r.URL.Path, p.prefix) + roomPath = strings.TrimLeft(roomPath, "/") + if roomPath == "" { + http.NotFound(w, r) + return + } + + // get room name + roomName, doRedir := roomPath, false + if i := strings.Index(roomPath, "/"); i != -1 { + roomName = roomPath[:i] + } else { + doRedir = true + } + + // get proxy by room name + p.mu.RLock() + proxy, ok := p.handlers[roomName] + p.mu.RUnlock() + + // if room not found + if !ok { + http.NotFound(w, r) + return + } + + // redirect to room ending with / + if doRedir { + r.URL.Path += "/" + http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect) + return + } + + // strip prefix from proxy + pathPrefix := path.Join(p.prefix, roomName) + proxy = http.StripPrefix(pathPrefix, proxy) + + // handle by proxy + proxy.ServeHTTP(w, r) +} diff --git a/internal/server/manager.go b/internal/server/manager.go index 5bd7e19..61ff432 100644 --- a/internal/server/manager.go +++ b/internal/server/manager.go @@ -24,7 +24,7 @@ type ServerManagerCtx struct { config *config.Server } -func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server) *ServerManagerCtx { +func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server, handler http.Handler) *ServerManagerCtx { logger := log.With().Str("module", "server").Logger() router := chi.NewRouter() @@ -73,6 +73,8 @@ func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server) logger.Info().Msgf("with pprof endpoint at %s", pprofPath) } + router.Handle("/*", handler) + // we could use custom 404 router.NotFound(http.NotFound) diff --git a/internal/types/proxy.go b/internal/types/proxy.go new file mode 100644 index 0000000..f5b2cb0 --- /dev/null +++ b/internal/types/proxy.go @@ -0,0 +1,8 @@ +package types + +import "net/http" + +type ProxyManager interface { + ServeHTTP(w http.ResponseWriter, r *http.Request) + Shutdown() error +} diff --git a/neko.go b/neko.go index 07ca2c3..9543751 100644 --- a/neko.go +++ b/neko.go @@ -13,6 +13,7 @@ import ( "github.com/m1k1o/neko-rooms/internal/api" "github.com/m1k1o/neko-rooms/internal/config" + "github.com/m1k1o/neko-rooms/internal/proxy" "github.com/m1k1o/neko-rooms/internal/pull" "github.com/m1k1o/neko-rooms/internal/room" "github.com/m1k1o/neko-rooms/internal/server" @@ -112,6 +113,7 @@ type MainCtx struct { roomManager *room.RoomManagerCtx pullManager *pull.PullManagerCtx apiManager *api.ApiManagerCtx + proxyManager *proxy.ProxyManagerCtx serverManager *server.ServerManagerCtx } @@ -143,17 +145,29 @@ func (main *MainCtx) Start() { main.Configs.API, ) + main.proxyManager = proxy.New( + client, + main.Configs.Room, + ) + main.proxyManager.Start() + main.serverManager = server.New( main.apiManager, main.Configs.Room.PathPrefix, main.Configs.Server, + main.proxyManager, ) main.serverManager.Start() } func (main *MainCtx) Shutdown() { - err := main.serverManager.Shutdown() + var err error + + err = main.serverManager.Shutdown() main.logger.Err(err).Msg("server manager shutdown") + + err = main.proxyManager.Shutdown() + main.logger.Err(err).Msg("proxy manager shutdown") } func (main *MainCtx) ServeCommand(cmd *cobra.Command, args []string) { From 8e93a71c398f2eb49a2fd3ec2da662bab9275fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 17:46:56 +0200 Subject: [PATCH 02/32] fix proxy for not running rooms. --- internal/proxy/manager.go | 116 ++++++++++++-------------------------- 1 file changed, 36 insertions(+), 80 deletions(-) diff --git a/internal/proxy/manager.go b/internal/proxy/manager.go index 6bb3d30..1ec5c2f 100644 --- a/internal/proxy/manager.go +++ b/internal/proxy/manager.go @@ -78,52 +78,41 @@ func (p *ProxyManagerCtx) Start() { break } + p.logger.Info(). + Str("action", msg.Action). + Str("name", name). + Str("host", host). + Msg("new docker event") + + p.mu.Lock() switch msg.Action { case "create": - p.logger.Info(). - Str("id", msg.ID). - Str("name", name). - Str("host", host). - Msg("container created") - + p.handlers[name] = nil case "start": - p.Add(name, host+":"+port) - - p.logger.Info(). - Str("id", msg.ID). - Str("name", name). - Str("host", host). - Msg("container started") - + p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "http", + Host: host + ":" + port, + }) case "stop": - p.Remove(name) - - p.logger.Info(). - Str("id", msg.ID). - Str("name", name). - Str("host", host). - Msg("container stopped") - + p.handlers[name] = nil case "destroy": - p.logger.Info(). - Str("id", msg.ID). - Str("name", name). - Str("host", host). - Msg("container destroyed") + delete(p.handlers, name) } + p.mu.Unlock() } } }() } func (p *ProxyManagerCtx) Shutdown() error { - p.Clear() - p.cancel() return p.ctx.Err() } -func (p *ProxyManagerCtx) refresh() error { +func (p *ProxyManagerCtx) Refresh() error { + p.mu.Lock() + defer p.mu.Unlock() + containers, err := p.client.ContainerList(p.ctx, dockerTypes.ContainerListOptions{ All: true, Filters: filters.NewArgs( @@ -135,13 +124,22 @@ func (p *ProxyManagerCtx) refresh() error { return err } - p.clear() + p.handlers = map[string]http.Handler{} for _, cont := range containers { name, port, ok := p.parseLabels(cont.Labels) - if ok { + if !ok { + continue + } + + if cont.State == "running" { host := cont.ID[:12] - p.add(name, host+":"+port) + p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "http", + Host: host + ":" + port, + }) + } else { + p.handlers[name] = nil } } @@ -159,53 +157,6 @@ func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (name string, po return } -func (p *ProxyManagerCtx) add(name, host string) { - p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{ - Scheme: "http", - Host: host, - }) - - p.logger.Debug().Str("name", name).Str("host", host).Msg("add handler") -} - -func (p *ProxyManagerCtx) remove(name string) { - delete(p.handlers, name) - p.logger.Debug().Str("name", name).Msg("remove handler") -} - -func (p *ProxyManagerCtx) clear() { - p.handlers = map[string]http.Handler{} - p.logger.Debug().Msg("clear handlers") -} - -func (p *ProxyManagerCtx) Refresh() error { - p.mu.Lock() - defer p.mu.Unlock() - - return p.refresh() -} - -func (p *ProxyManagerCtx) Add(name, host string) { - p.mu.Lock() - defer p.mu.Unlock() - - p.add(name, host) -} - -func (p *ProxyManagerCtx) Remove(name string) { - p.mu.Lock() - defer p.mu.Unlock() - - p.remove(name) -} - -func (p *ProxyManagerCtx) Clear() { - p.mu.Lock() - defer p.mu.Unlock() - - p.clear() -} - func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) { // only if has prefix if !strings.HasPrefix(r.URL.Path, p.prefix) { @@ -247,6 +198,11 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + if proxy == nil { + http.Error(w, "room exists but is not running", http.StatusBadGateway) + return + } + // strip prefix from proxy pathPrefix := path.Join(p.prefix, roomName) proxy = http.StripPrefix(pathPrefix, proxy) From 8fc2027f1d0b1e1cdcacc57dc69a8b1571dcc1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 18:11:44 +0200 Subject: [PATCH 03/32] move lobby to proxy. --- internal/api/lobby.go | 83 +++------------------------------------ internal/proxy/lobby.go | 79 +++++++++++++++++++++++++++++++++++++ internal/proxy/manager.go | 25 ++++++++---- 3 files changed, 102 insertions(+), 85 deletions(-) create mode 100644 internal/proxy/lobby.go diff --git a/internal/api/lobby.go b/internal/api/lobby.go index f7a4394..00fff1a 100644 --- a/internal/api/lobby.go +++ b/internal/api/lobby.go @@ -5,8 +5,7 @@ import ( "strings" "github.com/go-chi/chi" - - "github.com/m1k1o/neko-rooms/internal/utils" + "github.com/m1k1o/neko-rooms/internal/proxy" ) func (manager *ApiManagerCtx) RoomLobby(w http.ResponseWriter, r *http.Request) { @@ -18,91 +17,19 @@ func (manager *ApiManagerCtx) RoomLobby(w http.ResponseWriter, r *http.Request) response, err := manager.rooms.FindByName(roomName) if err != nil || response.Name != roomName { - manager.roomNotFound(w, r) + proxy.RoomNotFound(w, r) return } if !response.Running { - manager.roomNotRunning(w, r) + proxy.RoomNotRunning(w, r) return } if strings.Contains(response.Status, "starting") { - manager.roomNotReady(w, r) + proxy.RoomNotReady(w, r) return } - manager.roomReady(w, r) -} - -func (manager *ApiManagerCtx) roomNotFound(w http.ResponseWriter, r *http.Request) { - utils.Swal2Response(w, ` -
-
-
X
-
-

Room not found!

-
-
-
The room you are trying to join does not exist.
-
- `) -} - -func (manager *ApiManagerCtx) roomNotRunning(w http.ResponseWriter, r *http.Request) { - utils.Swal2Response(w, ` -
-
-
!
-
-

Room is not running!

-
-
-
The room you are trying to join is not running.
-
- `) -} - -func (manager *ApiManagerCtx) roomNotReady(w http.ResponseWriter, r *http.Request) { - utils.Swal2Response(w, ` - - -
-
-
i
-
-

Room is not ready, yet!

-
-
-
Please wait, until this room is ready so you can join. This should happen any second now.
-
-
-
- -
- `) -} - -func (manager *ApiManagerCtx) roomReady(w http.ResponseWriter, r *http.Request) { - utils.Swal2Response(w, ` -
-
-
- -
-
-
-

Room is ready!

-
-
-
Requested room is ready, you can join now.
-
Try to reload page.
-
-
- -
-
- If you see this page after refresh,
it can mean misconfiguration on your side.
-
- `) + proxy.RoomReady(w, r) } diff --git a/internal/proxy/lobby.go b/internal/proxy/lobby.go new file mode 100644 index 0000000..fc81add --- /dev/null +++ b/internal/proxy/lobby.go @@ -0,0 +1,79 @@ +package proxy + +import ( + "net/http" + + "github.com/m1k1o/neko-rooms/internal/utils" +) + +func RoomNotFound(w http.ResponseWriter, r *http.Request) { + utils.Swal2Response(w, ` +
+
+
X
+
+

Room not found!

+
+
+
The room you are trying to join does not exist.
+
+ `) +} + +func RoomNotRunning(w http.ResponseWriter, r *http.Request) { + utils.Swal2Response(w, ` +
+
+
!
+
+

Room is not running!

+
+
+
The room you are trying to join is not running.
+
+ `) +} + +func RoomNotReady(w http.ResponseWriter, r *http.Request) { + utils.Swal2Response(w, ` + + +
+
+
i
+
+

Room is not ready, yet!

+
+
+
Please wait, until this room is ready so you can join. This should happen any second now.
+
+
+
+ +
+ `) +} + +func RoomReady(w http.ResponseWriter, r *http.Request) { + utils.Swal2Response(w, ` +
+
+
+ +
+
+
+

Room is ready!

+
+
+
Requested room is ready, you can join now.
+
Try to reload page.
+
+
+ +
+
+ If you see this page after refresh,
it can mean misconfiguration on your side.
+
+ `) +} diff --git a/internal/proxy/manager.go b/internal/proxy/manager.go index 1ec5c2f..13c8ed4 100644 --- a/internal/proxy/manager.go +++ b/internal/proxy/manager.go @@ -27,7 +27,7 @@ type ProxyManagerCtx struct { prefix string client *dockerClient.Client config *config.Room - handlers map[string]http.Handler + handlers map[string]*httputil.ReverseProxy } func New(client *dockerClient.Client, config *config.Room) *ProxyManagerCtx { @@ -43,7 +43,7 @@ func New(client *dockerClient.Client, config *config.Room) *ProxyManagerCtx { prefix: prefix, client: client, config: config, - handlers: map[string]http.Handler{}, + handlers: map[string]*httputil.ReverseProxy{}, } } @@ -62,6 +62,7 @@ func (p *ProxyManagerCtx) Start() { filters.Arg("label", fmt.Sprintf("m1k1o.neko_rooms.instance=%s", p.config.InstanceName)), filters.Arg("event", "create"), filters.Arg("event", "start"), + //filters.Arg("event", "health_status"), filters.Arg("event", "stop"), filters.Arg("event", "destroy"), ), @@ -124,7 +125,7 @@ func (p *ProxyManagerCtx) Refresh() error { return err } - p.handlers = map[string]http.Handler{} + p.handlers = map[string]*httputil.ReverseProxy{} for _, cont := range containers { name, port, ok := p.parseLabels(cont.Labels) @@ -187,7 +188,7 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) { // if room not found if !ok { - http.NotFound(w, r) + RoomNotFound(w, r) return } @@ -198,15 +199,25 @@ func (p *ProxyManagerCtx) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } + // if room not running if proxy == nil { - http.Error(w, "room exists but is not running", http.StatusBadGateway) + RoomNotRunning(w, r) return } + // handle not ready room + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + if r.URL.Path == "/" { + RoomNotReady(w, r) + } + + p.logger.Err(err).Str("room", roomName).Msg("proxying error") + } + // strip prefix from proxy pathPrefix := path.Join(p.prefix, roomName) - proxy = http.StripPrefix(pathPrefix, proxy) + handler := http.StripPrefix(pathPrefix, proxy) // handle by proxy - proxy.ServeHTTP(w, r) + handler.ServeHTTP(w, r) } From 7c728ebd186692a66c23b995e01494dec3c9a873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 18:25:32 +0200 Subject: [PATCH 04/32] internal proxy & admin path prefix. --- internal/config/server.go | 17 +++++++++++++ internal/server/manager.go | 49 ++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/internal/config/server.go b/internal/config/server.go index 7d35626..9cd47ca 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -12,6 +12,9 @@ type Server struct { Static string Proxy bool PProf bool + + InternalProxy bool + AdminPathPrefix string } func (Server) Init(cmd *cobra.Command) error { @@ -45,6 +48,17 @@ func (Server) Init(cmd *cobra.Command) error { return err } + // WIP. + cmd.PersistentFlags().Bool("internal_proxy", false, "use internal proxy instead of traefik") + if err := viper.BindPFlag("internal_proxy", cmd.PersistentFlags().Lookup("internal_proxy")); err != nil { + return err + } + + cmd.PersistentFlags().String("admin_path_prefix", "/", "set custom path prefix for admin") + if err := viper.BindPFlag("admin_path_prefix", cmd.PersistentFlags().Lookup("admin_path_prefix")); err != nil { + return err + } + return nil } @@ -55,4 +69,7 @@ func (s *Server) Set() { s.Static = viper.GetString("static") s.Proxy = viper.GetBool("proxy") s.PProf = viper.GetBool("pprof") + + s.InternalProxy = viper.GetBool("internal_proxy") + s.AdminPathPrefix = viper.GetString("admin_path_prefix") } diff --git a/internal/server/manager.go b/internal/server/manager.go index 61ff432..c8a589f 100644 --- a/internal/server/manager.go +++ b/internal/server/manager.go @@ -4,7 +4,6 @@ import ( "context" "net/http" "os" - "path" "time" "github.com/go-chi/chi" @@ -24,7 +23,7 @@ type ServerManagerCtx struct { config *config.Server } -func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server, handler http.Handler) *ServerManagerCtx { +func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server, proxyHandler http.Handler) *ServerManagerCtx { logger := log.With().Str("module", "server").Logger() router := chi.NewRouter() @@ -49,31 +48,39 @@ func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server, MaxAge: 300, // Maximum value not ignored by any of major browsers })) - router.Route("/api", ApiManager.Mount) - - // serve static files - if config.Static != "" { - fs := http.FileServer(http.Dir(config.Static)) - router.Get("/*", func(w http.ResponseWriter, r *http.Request) { - if _, err := os.Stat(config.Static + r.URL.Path); !os.IsNotExist(err) { - fs.ServeHTTP(w, r) - } else { - http.NotFound(w, r) - } - }) - } - - // add simple lobby room - router.Get(path.Join("/", pathPrefix, "{roomName}"), ApiManager.RoomLobby) - router.Get(path.Join("/", pathPrefix, "{roomName}")+"/", ApiManager.RoomLobby) - // mount pprof endpoint if config.PProf { withPProf(router) logger.Info().Msgf("with pprof endpoint at %s", pprofPath) } - router.Handle("/*", handler) + // admin page + router.Route(config.AdminPathPrefix, func(r chi.Router) { + r.Route("/api", ApiManager.Mount) + + // serve static files + if config.Static != "" { + fs := http.FileServer(http.Dir(config.Static)) + r.Get("/*", func(w http.ResponseWriter, r *http.Request) { + if _, err := os.Stat(config.Static + r.URL.Path); !os.IsNotExist(err) { + fs.ServeHTTP(w, r) + } else { + http.NotFound(w, r) + } + }) + } + }) + + // rooms page + router.Route(pathPrefix, func(r chi.Router) { + if config.InternalProxy { + r.Handle("/*", proxyHandler) + } else { + // add simple lobby room + r.Get("/{roomName}", ApiManager.RoomLobby) + r.Get("/{roomName}/", ApiManager.RoomLobby) + } + }) // we could use custom 404 router.NotFound(http.NotFound) From 0da36c0b61ac2d092aaa07d9a5ba52a5d07ffdec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 18:47:23 +0200 Subject: [PATCH 05/32] traefik enable in config. --- internal/config/room.go | 1 + internal/proxy/manager.go | 15 +++---- internal/room/manager.go | 87 +++++++++++++++++++-------------------- neko.go | 3 ++ 4 files changed, 53 insertions(+), 53 deletions(-) diff --git a/internal/config/room.go b/internal/config/room.go index 272f524..e66deb0 100644 --- a/internal/config/room.go +++ b/internal/config/room.go @@ -33,6 +33,7 @@ type Room struct { InstanceName string InstanceUrl *url.URL + TraefikEnabled bool // TODO: now set from Server config, needed refactor TraefikDomain string TraefikEntrypoint string TraefikCertresolver string diff --git a/internal/proxy/manager.go b/internal/proxy/manager.go index 13c8ed4..75a061d 100644 --- a/internal/proxy/manager.go +++ b/internal/proxy/manager.go @@ -73,8 +73,7 @@ func (p *ProxyManagerCtx) Start() { case err := <-errs: p.logger.Info().Interface("err", err).Msg("eee") case msg := <-msgs: - host := msg.ID[:12] - name, port, ok := p.parseLabels(msg.Actor.Attributes) + name, host, ok := p.parseLabels(msg.Actor.Attributes) if !ok { break } @@ -92,7 +91,7 @@ func (p *ProxyManagerCtx) Start() { case "start": p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{ Scheme: "http", - Host: host + ":" + port, + Host: host, }) case "stop": p.handlers[name] = nil @@ -128,16 +127,15 @@ func (p *ProxyManagerCtx) Refresh() error { p.handlers = map[string]*httputil.ReverseProxy{} for _, cont := range containers { - name, port, ok := p.parseLabels(cont.Labels) + name, host, ok := p.parseLabels(cont.Labels) if !ok { continue } if cont.State == "running" { - host := cont.ID[:12] p.handlers[name] = httputil.NewSingleHostReverseProxy(&url.URL{ Scheme: "http", - Host: host + ":" + port, + Host: host, }) } else { p.handlers[name] = nil @@ -147,14 +145,13 @@ func (p *ProxyManagerCtx) Refresh() error { return nil } -func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (name string, port string, ok bool) { +func (p *ProxyManagerCtx) parseLabels(labels map[string]string) (name string, host string, ok bool) { name, ok = labels["m1k1o.neko_rooms.name"] if !ok { return } - // TODO: Do not use trafik for this. - port, ok = labels["traefik.http.services.neko-rooms-"+name+"-frontend.loadbalancer.server.port"] + host, ok = labels["m1k1o.neko_rooms.host"] return } diff --git a/internal/room/manager.go b/internal/room/manager.go index f8c89d7..f1fb693 100644 --- a/internal/room/manager.go +++ b/internal/room/manager.go @@ -112,6 +112,8 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro } } + containerName := manager.config.InstanceName + "-" + roomName + // // Allocate ports // @@ -154,48 +156,6 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro exposedPorts[port] = struct{}{} } - // - // Set traefik labels - // - - containerName := manager.config.InstanceName + "-" + roomName - pathPrefix := path.Join("/", manager.config.PathPrefix, roomName) - - // create traefik rule - traefikRule := "PathPrefix(`" + pathPrefix + "`)" - if manager.config.TraefikDomain != "" && manager.config.TraefikDomain != "*" { - // match *.domain.tld as subdomain - if strings.HasPrefix(manager.config.TraefikDomain, "*.") { - traefikRule = fmt.Sprintf( - "Host(`%s.%s`)", - roomName, - strings.TrimPrefix(manager.config.TraefikDomain, "*."), - ) - } else { - traefikRule += " && Host(`" + manager.config.TraefikDomain + "`)" - } - } else { - traefikRule += " && HostRegexp(`{host:.+}`)" - } - - traefikLabels := map[string]string{ - "traefik.enable": "true", - "traefik.http.services." + containerName + "-frontend.loadbalancer.server.port": fmt.Sprintf("%d", frontendPort), - "traefik.http.routers." + containerName + ".entrypoints": manager.config.TraefikEntrypoint, - "traefik.http.routers." + containerName + ".rule": traefikRule, - "traefik.http.middlewares." + containerName + "-rdr.redirectregex.regex": pathPrefix + "$$", - "traefik.http.middlewares." + containerName + "-rdr.redirectregex.replacement": pathPrefix + "/", - "traefik.http.middlewares." + containerName + "-prf.stripprefix.prefixes": pathPrefix + "/", - "traefik.http.routers." + containerName + ".middlewares": containerName + "-rdr," + containerName + "-prf", - "traefik.http.routers." + containerName + ".service": containerName + "-frontend", - } - - // optional HTTPS - if manager.config.TraefikCertresolver != "" { - traefikLabels["traefik.http.routers."+containerName+".tls"] = "true" - traefikLabels["traefik.http.routers."+containerName+".tls.certresolver"] = manager.config.TraefikCertresolver - } - // // Set internal labels // @@ -217,8 +177,47 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro BrowserPolicy: browserPolicyLabels, }) - for k, v := range traefikLabels { - labels[k] = v + // + // Set traefik labels + // + + if manager.config.TraefikEnabled { + pathPrefix := path.Join("/", manager.config.PathPrefix, roomName) + + // create traefik rule + traefikRule := "PathPrefix(`" + pathPrefix + "`)" + if manager.config.TraefikDomain != "" && manager.config.TraefikDomain != "*" { + // match *.domain.tld as subdomain + if strings.HasPrefix(manager.config.TraefikDomain, "*.") { + traefikRule = fmt.Sprintf( + "Host(`%s.%s`)", + roomName, + strings.TrimPrefix(manager.config.TraefikDomain, "*."), + ) + } else { + traefikRule += " && Host(`" + manager.config.TraefikDomain + "`)" + } + } else { + traefikRule += " && HostRegexp(`{host:.+}`)" + } + + labels["traefik.enable"] = "true" + labels["traefik.http.services."+containerName+"-frontend.loadbalancer.server.port"] = fmt.Sprintf("%d", frontendPort) + labels["traefik.http.routers."+containerName+".entrypoints"] = manager.config.TraefikEntrypoint + labels["traefik.http.routers."+containerName+".rule"] = traefikRule + labels["traefik.http.middlewares."+containerName+"-rdr.redirectregex.regex"] = pathPrefix + "$$" + labels["traefik.http.middlewares."+containerName+"-rdr.redirectregex.replacement"] = pathPrefix + "/" + labels["traefik.http.middlewares."+containerName+"-prf.stripprefix.prefixes"] = pathPrefix + "/" + labels["traefik.http.routers."+containerName+".middlewares"] = containerName + "-rdr," + containerName + "-prf" + labels["traefik.http.routers."+containerName+".service"] = containerName + "-frontend" + + // optional HTTPS + if manager.config.TraefikCertresolver != "" { + labels["traefik.http.routers."+containerName+".tls"] = "true" + labels["traefik.http.routers."+containerName+".tls.certresolver"] = manager.config.TraefikCertresolver + } + } else { + labels["m1k1o.neko_rooms.host"] = fmt.Sprintf("%s:%d", containerName, frontendPort) } // add custom labels diff --git a/neko.go b/neko.go index 9543751..5eadb2f 100644 --- a/neko.go +++ b/neko.go @@ -129,6 +129,9 @@ func (main *MainCtx) Start() { main.logger.Info().Msg("successfully connected to docker client") } + // TODO: Refactor. + main.Configs.Room.TraefikEnabled = !main.Configs.Server.InternalProxy + main.roomManager = room.New( client, main.Configs.Room, From d5068ae39bf7094cd64ac52b479111c01c781a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 18:57:56 +0200 Subject: [PATCH 06/32] flexible url if no traefik. --- internal/room/labels.go | 8 ++++++-- internal/room/manager.go | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/room/labels.go b/internal/room/labels.go index ae5b0c5..25e2abb 100644 --- a/internal/room/labels.go +++ b/internal/room/labels.go @@ -29,7 +29,7 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab url, ok := labels["m1k1o.neko_rooms.url"] if !ok { - return nil, fmt.Errorf("damaged container labels: url not found") + url = manager.config.GetRoomUrl(name) } nekoImage, ok := labels["m1k1o.neko_rooms.neko_image"] @@ -90,13 +90,17 @@ func (manager *RoomManagerCtx) extractLabels(labels map[string]string) (*RoomLab func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]string { labelsMap := map[string]string{ "m1k1o.neko_rooms.name": labels.Name, - "m1k1o.neko_rooms.url": labels.URL, "m1k1o.neko_rooms.instance": manager.config.InstanceName, "m1k1o.neko_rooms.epr.min": fmt.Sprintf("%d", labels.Epr.Min), "m1k1o.neko_rooms.epr.max": fmt.Sprintf("%d", labels.Epr.Max), "m1k1o.neko_rooms.neko_image": labels.NekoImage, } + // Only when using traefik is the URL fixed with the room itself and not with neko-rooms. + if manager.config.TraefikEnabled { + labelsMap["m1k1o.neko_rooms.url"] = manager.config.GetRoomUrl(labels.Name) + } + if labels.BrowserPolicy != nil { labelsMap["m1k1o.neko_rooms.browser_policy"] = "true" labelsMap["m1k1o.neko_rooms.browser_policy.type"] = string(labels.BrowserPolicy.Type) diff --git a/internal/room/manager.go b/internal/room/manager.go index f1fb693..ba26a15 100644 --- a/internal/room/manager.go +++ b/internal/room/manager.go @@ -170,7 +170,6 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro labels := manager.serializeLabels(RoomLabels{ Name: roomName, - URL: manager.config.GetRoomUrl(roomName), Epr: epr, NekoImage: settings.NekoImage, From 4d2b268d32a5c76ef33af544e6d7d12887d3888a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 20:07:18 +0200 Subject: [PATCH 07/32] traefik enabled. --- internal/config/room.go | 48 ++++++++++++++++++++++++-------------- internal/config/server.go | 8 ------- internal/room/labels.go | 2 +- internal/room/manager.go | 25 +++++++++++--------- internal/server/manager.go | 6 ++--- neko.go | 5 +--- 6 files changed, 50 insertions(+), 44 deletions(-) diff --git a/internal/config/room.go b/internal/config/room.go index e66deb0..6fffccd 100644 --- a/internal/config/room.go +++ b/internal/config/room.go @@ -13,6 +13,15 @@ import ( "github.com/spf13/viper" ) +type Traefik struct { + Enabled bool + Domain string + Entrypoint string + Certresolver string + Network string + Port string // deprecated +} + type Room struct { Mux bool EprMin uint16 @@ -33,12 +42,7 @@ type Room struct { InstanceName string InstanceUrl *url.URL - TraefikEnabled bool // TODO: now set from Server config, needed refactor - TraefikDomain string - TraefikEntrypoint string - TraefikCertresolver string - TraefikNetwork string - TraefikPort string // deprecated + Traefik Traefik } func (Room) Init(cmd *cobra.Command) error { @@ -125,6 +129,11 @@ func (Room) Init(cmd *cobra.Command) error { // Traefik + cmd.PersistentFlags().Bool("traefik.enabled", true, "traefik: enabled or disabled") + if err := viper.BindPFlag("traefik.enabled", cmd.PersistentFlags().Lookup("traefik.enabled")); err != nil { + return err + } + cmd.PersistentFlags().String("traefik.domain", "", "traefik: domain on which will be container hosted (if empty or '*', match all; for neko-rooms as subdomain use '*.domain.tld')") if err := viper.BindPFlag("traefik.domain", cmd.PersistentFlags().Lookup("traefik.domain")); err != nil { return err @@ -225,14 +234,15 @@ func (s *Room) Set() { } } - s.TraefikDomain = viper.GetString("traefik.domain") - s.TraefikEntrypoint = viper.GetString("traefik.entrypoint") - s.TraefikCertresolver = viper.GetString("traefik.certresolver") - s.TraefikNetwork = viper.GetString("traefik.network") + s.Traefik.Enabled = viper.GetBool("traefik.enabled") + s.Traefik.Domain = viper.GetString("traefik.domain") + s.Traefik.Entrypoint = viper.GetString("traefik.entrypoint") + s.Traefik.Certresolver = viper.GetString("traefik.certresolver") + s.Traefik.Network = viper.GetString("traefik.network") // deprecated - s.TraefikPort = viper.GetString("traefik.port") - if s.TraefikPort != "" { + s.Traefik.Port = viper.GetString("traefik.port") + if s.Traefik.Port != "" { if s.InstanceUrl != nil { log.Warn().Msg("deprecated `traefik.port` config item is ignored when `instance.url` is set") } else { @@ -252,17 +262,21 @@ func (s *Room) GetInstanceUrl() url.URL { Path: "/", } - if s.TraefikCertresolver != "" { + if !s.Traefik.Enabled { + return instanceUrl + } + + if s.Traefik.Certresolver != "" { instanceUrl.Scheme = "https" } - if s.TraefikDomain != "" && s.TraefikDomain != "*" { - instanceUrl.Host = s.TraefikDomain + if s.Traefik.Domain != "" && s.Traefik.Domain != "*" { + instanceUrl.Host = s.Traefik.Domain } // deprecated - if s.TraefikPort != "" { - instanceUrl.Host += ":" + s.TraefikPort + if s.Traefik.Port != "" { + instanceUrl.Host += ":" + s.Traefik.Port } return instanceUrl diff --git a/internal/config/server.go b/internal/config/server.go index 9cd47ca..1f119f9 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -13,7 +13,6 @@ type Server struct { Proxy bool PProf bool - InternalProxy bool AdminPathPrefix string } @@ -48,12 +47,6 @@ func (Server) Init(cmd *cobra.Command) error { return err } - // WIP. - cmd.PersistentFlags().Bool("internal_proxy", false, "use internal proxy instead of traefik") - if err := viper.BindPFlag("internal_proxy", cmd.PersistentFlags().Lookup("internal_proxy")); err != nil { - return err - } - cmd.PersistentFlags().String("admin_path_prefix", "/", "set custom path prefix for admin") if err := viper.BindPFlag("admin_path_prefix", cmd.PersistentFlags().Lookup("admin_path_prefix")); err != nil { return err @@ -70,6 +63,5 @@ func (s *Server) Set() { s.Proxy = viper.GetBool("proxy") s.PProf = viper.GetBool("pprof") - s.InternalProxy = viper.GetBool("internal_proxy") s.AdminPathPrefix = viper.GetString("admin_path_prefix") } diff --git a/internal/room/labels.go b/internal/room/labels.go index 25e2abb..9d8ea96 100644 --- a/internal/room/labels.go +++ b/internal/room/labels.go @@ -97,7 +97,7 @@ func (manager *RoomManagerCtx) serializeLabels(labels RoomLabels) map[string]str } // Only when using traefik is the URL fixed with the room itself and not with neko-rooms. - if manager.config.TraefikEnabled { + if manager.config.Traefik.Enabled { labelsMap["m1k1o.neko_rooms.url"] = manager.config.GetRoomUrl(labels.Name) } diff --git a/internal/room/manager.go b/internal/room/manager.go index ba26a15..452feb8 100644 --- a/internal/room/manager.go +++ b/internal/room/manager.go @@ -180,21 +180,21 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro // Set traefik labels // - if manager.config.TraefikEnabled { + if t := manager.config.Traefik; t.Enabled { pathPrefix := path.Join("/", manager.config.PathPrefix, roomName) // create traefik rule traefikRule := "PathPrefix(`" + pathPrefix + "`)" - if manager.config.TraefikDomain != "" && manager.config.TraefikDomain != "*" { + if t.Domain != "" && t.Domain != "*" { // match *.domain.tld as subdomain - if strings.HasPrefix(manager.config.TraefikDomain, "*.") { + if strings.HasPrefix(t.Domain, "*.") { traefikRule = fmt.Sprintf( "Host(`%s.%s`)", roomName, - strings.TrimPrefix(manager.config.TraefikDomain, "*."), + strings.TrimPrefix(t.Domain, "*."), ) } else { - traefikRule += " && Host(`" + manager.config.TraefikDomain + "`)" + traefikRule += " && Host(`" + t.Domain + "`)" } } else { traefikRule += " && HostRegexp(`{host:.+}`)" @@ -202,7 +202,7 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro labels["traefik.enable"] = "true" labels["traefik.http.services."+containerName+"-frontend.loadbalancer.server.port"] = fmt.Sprintf("%d", frontendPort) - labels["traefik.http.routers."+containerName+".entrypoints"] = manager.config.TraefikEntrypoint + labels["traefik.http.routers."+containerName+".entrypoints"] = t.Entrypoint labels["traefik.http.routers."+containerName+".rule"] = traefikRule labels["traefik.http.middlewares."+containerName+"-rdr.redirectregex.regex"] = pathPrefix + "$$" labels["traefik.http.middlewares."+containerName+"-rdr.redirectregex.replacement"] = pathPrefix + "/" @@ -211,9 +211,9 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro labels["traefik.http.routers."+containerName+".service"] = containerName + "-frontend" // optional HTTPS - if manager.config.TraefikCertresolver != "" { + if t.Certresolver != "" { labels["traefik.http.routers."+containerName+".tls"] = "true" - labels["traefik.http.routers."+containerName+".tls.certresolver"] = manager.config.TraefikCertresolver + labels["traefik.http.routers."+containerName+".tls.certresolver"] = t.Certresolver } } else { labels["m1k1o.neko_rooms.host"] = fmt.Sprintf("%s:%d", containerName, frontendPort) @@ -224,8 +224,11 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro // replace dynamic values in labels label = strings.Replace(label, "{containerName}", containerName, -1) label = strings.Replace(label, "{roomName}", roomName, -1) - label = strings.Replace(label, "{traefikEntrypoint}", manager.config.TraefikEntrypoint, -1) - label = strings.Replace(label, "{traefikCertresolver}", manager.config.TraefikCertresolver, -1) + + if t := manager.config.Traefik; t.Enabled { + label = strings.Replace(label, "{traefikEntrypoint}", t.Entrypoint, -1) + label = strings.Replace(label, "{traefikCertresolver}", t.Certresolver, -1) + } v := strings.SplitN(label, "=", 2) if len(v) != 2 { @@ -434,7 +437,7 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ - manager.config.TraefikNetwork: {}, + manager.config.Traefik.Network: {}, // TODO: Refactor if not using traefik. }, } diff --git a/internal/server/manager.go b/internal/server/manager.go index c8a589f..50b74f2 100644 --- a/internal/server/manager.go +++ b/internal/server/manager.go @@ -23,7 +23,7 @@ type ServerManagerCtx struct { config *config.Server } -func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server, proxyHandler http.Handler) *ServerManagerCtx { +func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Server, proxyHandler http.Handler) *ServerManagerCtx { logger := log.With().Str("module", "server").Logger() router := chi.NewRouter() @@ -72,8 +72,8 @@ func New(ApiManager types.ApiManager, pathPrefix string, config *config.Server, }) // rooms page - router.Route(pathPrefix, func(r chi.Router) { - if config.InternalProxy { + router.Route(roomConfig.PathPrefix, func(r chi.Router) { + if !roomConfig.Traefik.Enabled { r.Handle("/*", proxyHandler) } else { // add simple lobby room diff --git a/neko.go b/neko.go index 5eadb2f..9840d81 100644 --- a/neko.go +++ b/neko.go @@ -129,9 +129,6 @@ func (main *MainCtx) Start() { main.logger.Info().Msg("successfully connected to docker client") } - // TODO: Refactor. - main.Configs.Room.TraefikEnabled = !main.Configs.Server.InternalProxy - main.roomManager = room.New( client, main.Configs.Room, @@ -156,7 +153,7 @@ func (main *MainCtx) Start() { main.serverManager = server.New( main.apiManager, - main.Configs.Room.PathPrefix, + main.Configs.Room, main.Configs.Server, main.proxyManager, ) From 2be3852bf4be3cbe5b87026699fe5e2ed42e564a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 20:29:50 +0200 Subject: [PATCH 08/32] traefik.network -> instance.network. --- internal/config/room.go | 27 ++++++++++++++++++++++----- internal/room/manager.go | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/internal/config/room.go b/internal/config/room.go index 6fffccd..b39b60b 100644 --- a/internal/config/room.go +++ b/internal/config/room.go @@ -18,7 +18,6 @@ type Traefik struct { Domain string Entrypoint string Certresolver string - Network string Port string // deprecated } @@ -39,8 +38,9 @@ type Room struct { MountsWhitelist []string - InstanceName string - InstanceUrl *url.URL + InstanceName string + InstanceUrl *url.URL + InstanceNetwork string Traefik Traefik } @@ -127,6 +127,11 @@ func (Room) Init(cmd *cobra.Command) error { return err } + cmd.PersistentFlags().String("instance.network", "", "docker network that will be used for this instance to communicate with rooms") + if err := viper.BindPFlag("instance.network", cmd.PersistentFlags().Lookup("instance.network")); err != nil { + return err + } + // Traefik cmd.PersistentFlags().Bool("traefik.enabled", true, "traefik: enabled or disabled") @@ -149,7 +154,7 @@ func (Room) Init(cmd *cobra.Command) error { return err } - cmd.PersistentFlags().String("traefik.network", "traefik", "traefik: docker network name") + cmd.PersistentFlags().String("traefik.network", "traefik", "traefik: docker network name (deprecated, use instance.network)") if err := viper.BindPFlag("traefik.network", cmd.PersistentFlags().Lookup("traefik.network")); err != nil { return err } @@ -234,11 +239,23 @@ func (s *Room) Set() { } } + s.InstanceNetwork = viper.GetString("instance.network") + s.Traefik.Enabled = viper.GetBool("traefik.enabled") s.Traefik.Domain = viper.GetString("traefik.domain") s.Traefik.Entrypoint = viper.GetString("traefik.entrypoint") s.Traefik.Certresolver = viper.GetString("traefik.certresolver") - s.Traefik.Network = viper.GetString("traefik.network") + + // deprecated + traefikNetwork := viper.GetString("traefik.network") + if traefikNetwork != "" { + if s.InstanceNetwork != "" { + log.Warn().Msg("deprecated `traefik.network` config item is ignored when `instance.network` is set") + } else { + log.Warn().Msg("you are using deprecated `traefik.network` config item, you should consider moving to `instance.network`") + s.InstanceNetwork = traefikNetwork + } + } // deprecated s.Traefik.Port = viper.GetString("traefik.port") diff --git a/internal/room/manager.go b/internal/room/manager.go index 452feb8..0ae815e 100644 --- a/internal/room/manager.go +++ b/internal/room/manager.go @@ -437,7 +437,7 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ - manager.config.Traefik.Network: {}, // TODO: Refactor if not using traefik. + manager.config.InstanceNetwork: {}, }, } From 9f6c8efb2adf138af0f46957be39de4dcf96f662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 20:33:16 +0200 Subject: [PATCH 09/32] set traefik config only if enabled. --- internal/config/room.go | 66 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/internal/config/room.go b/internal/config/room.go index b39b60b..730aa9c 100644 --- a/internal/config/room.go +++ b/internal/config/room.go @@ -242,28 +242,30 @@ func (s *Room) Set() { s.InstanceNetwork = viper.GetString("instance.network") s.Traefik.Enabled = viper.GetBool("traefik.enabled") - s.Traefik.Domain = viper.GetString("traefik.domain") - s.Traefik.Entrypoint = viper.GetString("traefik.entrypoint") - s.Traefik.Certresolver = viper.GetString("traefik.certresolver") - - // deprecated - traefikNetwork := viper.GetString("traefik.network") - if traefikNetwork != "" { - if s.InstanceNetwork != "" { - log.Warn().Msg("deprecated `traefik.network` config item is ignored when `instance.network` is set") - } else { - log.Warn().Msg("you are using deprecated `traefik.network` config item, you should consider moving to `instance.network`") - s.InstanceNetwork = traefikNetwork + if s.Traefik.Enabled { + s.Traefik.Domain = viper.GetString("traefik.domain") + s.Traefik.Entrypoint = viper.GetString("traefik.entrypoint") + s.Traefik.Certresolver = viper.GetString("traefik.certresolver") + + // deprecated + traefikNetwork := viper.GetString("traefik.network") + if traefikNetwork != "" { + if s.InstanceNetwork != "" { + log.Warn().Msg("deprecated `traefik.network` config item is ignored when `instance.network` is set") + } else { + log.Warn().Msg("you are using deprecated `traefik.network` config item, you should consider moving to `instance.network`") + s.InstanceNetwork = traefikNetwork + } } - } - // deprecated - s.Traefik.Port = viper.GetString("traefik.port") - if s.Traefik.Port != "" { - if s.InstanceUrl != nil { - log.Warn().Msg("deprecated `traefik.port` config item is ignored when `instance.url` is set") - } else { - log.Warn().Msg("you are using deprecated `traefik.port` config item, you should consider moving to `instance.url`") + // deprecated + s.Traefik.Port = viper.GetString("traefik.port") + if s.Traefik.Port != "" { + if s.InstanceUrl != nil { + log.Warn().Msg("deprecated `traefik.port` config item is ignored when `instance.url` is set") + } else { + log.Warn().Msg("you are using deprecated `traefik.port` config item, you should consider moving to `instance.url`") + } } } } @@ -279,21 +281,19 @@ func (s *Room) GetInstanceUrl() url.URL { Path: "/", } - if !s.Traefik.Enabled { - return instanceUrl - } - - if s.Traefik.Certresolver != "" { - instanceUrl.Scheme = "https" - } + if s.Traefik.Enabled { + if s.Traefik.Certresolver != "" { + instanceUrl.Scheme = "https" + } - if s.Traefik.Domain != "" && s.Traefik.Domain != "*" { - instanceUrl.Host = s.Traefik.Domain - } + if s.Traefik.Domain != "" && s.Traefik.Domain != "*" { + instanceUrl.Host = s.Traefik.Domain + } - // deprecated - if s.Traefik.Port != "" { - instanceUrl.Host += ":" + s.Traefik.Port + // deprecated + if s.Traefik.Port != "" { + instanceUrl.Host += ":" + s.Traefik.Port + } } return instanceUrl From 46ba65741202d36dac8345b8b0f74d64d14a31c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 20:54:03 +0200 Subject: [PATCH 10/32] split traefik in dev. --- dev/serve | 35 +++++++++++++++++----------- dev/start | 49 ++++++++++++++++++++++++++++----------- dev/traefik/.env.example | 9 +++++++ dev/traefik/rebuild | 11 +++++++++ dev/traefik/serve | 24 +++++++++++++++++++ dev/traefik/start | 43 ++++++++++++++++++++++++++++++++++ dev/{ => traefik}/traefik | 8 +++---- 7 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 dev/traefik/.env.example create mode 100755 dev/traefik/rebuild create mode 100755 dev/traefik/serve create mode 100755 dev/traefik/start rename dev/{ => traefik}/traefik (85%) diff --git a/dev/serve b/dev/serve index c3131b5..d477f08 100755 --- a/dev/serve +++ b/dev/serve @@ -1,24 +1,31 @@ #!/bin/sh cd "$(dirname "$0")" -if [ ! -f "../.env" ] -then - echo "../.env file not found!" - exit 1 +if [ -z $APP_PORT ]; then + APP_PORT="8080" fi -export $(cat ../.env | sed 's/#.*//g' | xargs) +if [ -z $APP_HOST ]; then + for i in $(ifconfig -l 2>/dev/null); do + APP_HOST=$(ipconfig getifaddr $i) + if [ ! -z $APP_HOST ]; then + break + fi + done + + if [ -z $APP_HOST ]; then + APP_HOST=$(hostname -i 2>/dev/null) + fi +fi + +echo "Using app port: ${APP_PORT}" +echo "Using IP address: ${APP_HOST}" docker run --rm -it \ - --name="neko_rooms_client" \ - -v "${PWD}/../client:/app" \ - -e "TZ=${TZ}" \ - --net="${NEKO_ROOMS_TRAEFIK_NETWORK}" \ - -l "traefik.enable=true" \ - -l "traefik.http.services.neko-rooms-client-fe.loadbalancer.server.port=8080" \ - -l "traefik.http.routers.neko-rooms-client.entrypoints=${NEKO_ROOMS_TRAEFIK_ENTRYPOINT}" \ - -l "traefik.http.routers.neko-rooms-client.rule=Host(\`${NEKO_ROOMS_TRAEFIK_DOMAIN}\`)" \ + -p 8081:8080 \ + -e "API_PROXY=http://${APP_HOST}:${APP_PORT}" \ --user="$(id -u):$(id -g)" \ - --workdir="/app" \ + --volume "${PWD}/../client:/app" \ --entrypoint="npm" \ + --workdir="/app" \ node:14 run serve; diff --git a/dev/start b/dev/start index fbfb64e..fed2a65 100755 --- a/dev/start +++ b/dev/start @@ -1,13 +1,36 @@ #!/bin/sh cd "$(dirname "$0")" -if [ ! -f "../.env" ] -then - echo "../.env file not found!" - exit 1 +if [ -z $NEKO_ROOMS_PORT ]; then + NEKO_ROOMS_PORT="8080" fi -export $(cat ../.env | sed 's/#.*//g' | xargs) +if [ -z $NEKO_ROOMS_EPR ]; then + NEKO_ROOMS_EPR="52090-52099" +fi + +if [ -z $NEKO_ROOMS_NAT1TO1 ]; then + for i in $(ifconfig -l 2>/dev/null); do + NEKO_ROOMS_NAT1TO1=$(ipconfig getifaddr $i) + if [ ! -z $NEKO_ROOMS_NAT1TO1 ]; then + break + fi + done + + if [ -z $NEKO_ROOMS_NAT1TO1 ]; then + NEKO_ROOMS_NAT1TO1=$(hostname -i 2>/dev/null) + fi +fi + +NEKO_ROOMS_INSTANCE_NETWORK="neko-rooms-net" +docker network create --attachable "${NEKO_ROOMS_INSTANCE_NETWORK}"; + +trap on_exit EXIT + +on_exit() { + echo "Removing neko-rooms network" + docker network rm "${NEKO_ROOMS_INSTANCE_NETWORK}"; +} DATA_PATH="./data" mkdir -p "${DATA_PATH}" @@ -17,24 +40,22 @@ mkdir -p "${EXTERNAL_PATH}" docker run --rm -it \ --name="neko_rooms_server" \ + -p "${NEKO_ROOMS_PORT}:8080" \ -v "`realpath ..`:/app" \ -v "`realpath ${DATA_PATH}`:/data" \ -e "TZ=${TZ}" \ - -e "NEKO_ROOMS_EPR=${NEKO_ROOMS_EPR}" \ -e "NEKO_ROOMS_MUX=true" \ + -e "NEKO_ROOMS_EPR=${NEKO_ROOMS_EPR}" \ -e "NEKO_ROOMS_NAT1TO1=${NEKO_ROOMS_NAT1TO1}" \ + -e "NEKO_ROOMS_INSTANCE_URL=http://${NEKO_ROOMS_NAT1TO1}:${NEKO_ROOMS_PORT}/" \ + -e "NEKO_ROOMS_INSTANCE_NETWORK=${NEKO_ROOMS_INSTANCE_NETWORK}" \ -e "NEKO_ROOMS_STORAGE_INTERNAL=/data" \ -e "NEKO_ROOMS_STORAGE_EXTERNAL=`realpath ${DATA_PATH}`" \ -e "NEKO_ROOMS_MOUNTS_WHITELIST=`realpath ${EXTERNAL_PATH}`" \ - -e "NEKO_ROOMS_TRAEFIK_DOMAIN=${NEKO_ROOMS_TRAEFIK_DOMAIN}" \ - -e "NEKO_ROOMS_TRAEFIK_ENTRYPOINT=${NEKO_ROOMS_TRAEFIK_ENTRYPOINT}" \ - -e "NEKO_ROOMS_TRAEFIK_NETWORK=${NEKO_ROOMS_TRAEFIK_NETWORK}" \ + -e "NEKO_ROOMS_PATH_PREFIX=/room/" \ + -e "NEKO_ROOMS_TRAEFIK_ENABLED=false" \ -e 'DOCKER_API_VERSION=1.39' \ -v "/var/run/docker.sock:/var/run/docker.sock" \ - --net="${NEKO_ROOMS_TRAEFIK_NETWORK}" \ - -l "traefik.enable=true" \ - -l "traefik.http.services.neko-rooms-server-fe.loadbalancer.server.port=8080" \ - -l "traefik.http.routers.neko-rooms-server.entrypoints=${NEKO_ROOMS_TRAEFIK_ENTRYPOINT}" \ - -l "traefik.http.routers.neko-rooms-server.rule=Host(\`${NEKO_ROOMS_TRAEFIK_DOMAIN}\`) && PathPrefix(\`/api\`)" \ + --net="${NEKO_ROOMS_INSTANCE_NETWORK}" \ --entrypoint="/app/bin/neko_rooms" \ neko_rooms_img serve --bind :8080; diff --git a/dev/traefik/.env.example b/dev/traefik/.env.example new file mode 100644 index 0000000..be6d773 --- /dev/null +++ b/dev/traefik/.env.example @@ -0,0 +1,9 @@ +NEKO_ROOMS_EPR=59000-59049 +NEKO_ROOMS_NAT1TO1=192.168.1.20 + +NEKO_ROOMS_TRAEFIK_DOMAIN=neko-rooms.server.lan +NEKO_ROOMS_TRAEFIK_ENTRYPOINT=websecure +NEKO_ROOMS_TRAEFIK_NETWORK=neko-rooms-traefik +NEKO_ROOMS_TRAEFIK_CERTRESOLVER=lets-encrypt + +TZ=Europe/Vienna diff --git a/dev/traefik/rebuild b/dev/traefik/rebuild new file mode 100755 index 0000000..345525a --- /dev/null +++ b/dev/traefik/rebuild @@ -0,0 +1,11 @@ +#!/bin/sh +cd "$(dirname "$0")" + +set -e + +docker run --rm -it \ + -v "${PWD}/../../:/app" \ + --entrypoint="go" \ + neko_rooms_img build -o bin/neko_rooms cmd/neko_rooms/main.go + +./start diff --git a/dev/traefik/serve b/dev/traefik/serve new file mode 100755 index 0000000..9fc655d --- /dev/null +++ b/dev/traefik/serve @@ -0,0 +1,24 @@ +#!/bin/sh +cd "$(dirname "$0")" + +if [ ! -f ".env" ] +then + echo ".env file not found!" + exit 1 +fi + +export $(cat .env | sed 's/#.*//g' | xargs) + +docker run --rm -it \ + --name="neko_rooms_client" \ + -v "${PWD}/../../client:/app" \ + -e "TZ=${TZ}" \ + --net="${NEKO_ROOMS_TRAEFIK_NETWORK}" \ + -l "traefik.enable=true" \ + -l "traefik.http.services.neko-rooms-client-fe.loadbalancer.server.port=8080" \ + -l "traefik.http.routers.neko-rooms-client.entrypoints=${NEKO_ROOMS_TRAEFIK_ENTRYPOINT}" \ + -l "traefik.http.routers.neko-rooms-client.rule=Host(\`${NEKO_ROOMS_TRAEFIK_DOMAIN}\`)" \ + --user="$(id -u):$(id -g)" \ + --workdir="/app" \ + --entrypoint="npm" \ + node:14 run serve; diff --git a/dev/traefik/start b/dev/traefik/start new file mode 100755 index 0000000..7371258 --- /dev/null +++ b/dev/traefik/start @@ -0,0 +1,43 @@ +#!/bin/sh +cd "$(dirname "$0")" + +if [ ! -f ".env" ] +then + echo ".env file not found!" + exit 1 +fi + +export $(cat .env | sed 's/#.*//g' | xargs) + +DATA_PATH="../data" +mkdir -p "${DATA_PATH}" + +EXTERNAL_PATH="../ext" +mkdir -p "${EXTERNAL_PATH}" + +docker run --rm -it \ + --name="neko_rooms_server" \ + -v "`realpath ../../`:/app" \ + -v "`realpath ${DATA_PATH}`:/data" \ + -e "TZ=${TZ}" \ + -e "NEKO_ROOMS_EPR=${NEKO_ROOMS_EPR}" \ + -e "NEKO_ROOMS_MUX=true" \ + -e "NEKO_ROOMS_NAT1TO1=${NEKO_ROOMS_NAT1TO1}" \ + -e "NEKO_ROOMS_PATH_PREFIX=/room/" \ + -e "NEKO_ROOMS_ADMIN_PATH_PREFIX=/" \ + -e "NEKO_ROOMS_STORAGE_INTERNAL=/data" \ + -e "NEKO_ROOMS_STORAGE_EXTERNAL=`realpath ${DATA_PATH}`" \ + -e "NEKO_ROOMS_MOUNTS_WHITELIST=`realpath ${EXTERNAL_PATH}`" \ + -e "NEKO_ROOMS_TRAEFIK_ENABLED=true" \ + -e "NEKO_ROOMS_TRAEFIK_DOMAIN=${NEKO_ROOMS_TRAEFIK_DOMAIN}" \ + -e "NEKO_ROOMS_TRAEFIK_ENTRYPOINT=${NEKO_ROOMS_TRAEFIK_ENTRYPOINT}" \ + -e "NEKO_ROOMS_TRAEFIK_NETWORK=${NEKO_ROOMS_TRAEFIK_NETWORK}" \ + -e 'DOCKER_API_VERSION=1.39' \ + -v "/var/run/docker.sock:/var/run/docker.sock" \ + --net="${NEKO_ROOMS_TRAEFIK_NETWORK}" \ + -l "traefik.enable=true" \ + -l "traefik.http.services.neko-rooms-server-fe.loadbalancer.server.port=8080" \ + -l "traefik.http.routers.neko-rooms-server.entrypoints=${NEKO_ROOMS_TRAEFIK_ENTRYPOINT}" \ + -l "traefik.http.routers.neko-rooms-server.rule=Host(\`${NEKO_ROOMS_TRAEFIK_DOMAIN}\`) && (PathPrefix(\`/api\`) || PathPrefix(\`/room\`))" \ + --entrypoint="/app/bin/neko_rooms" \ + neko_rooms_img serve --bind :8080; diff --git a/dev/traefik b/dev/traefik/traefik similarity index 85% rename from dev/traefik rename to dev/traefik/traefik index 6970692..c787d74 100755 --- a/dev/traefik +++ b/dev/traefik/traefik @@ -1,13 +1,13 @@ #!/bin/sh cd "$(dirname "$0")" -if [ ! -f "../.env" ] +if [ ! -f ".env" ] then - echo "../.env file not found!" + echo ".env file not found!" exit 1 fi -export $(cat ../.env | sed 's/#.*//g' | xargs) +export $(cat .env | sed 's/#.*//g' | xargs) docker network create --attachable "${NEKO_ROOMS_TRAEFIK_NETWORK}"; @@ -22,7 +22,7 @@ docker run --rm -it \ --name="neko_rooms_traefik" \ -p "${1:-80}:80" \ -p "8080:8080" \ - -v "${PWD}/../:/app" \ + -v "${PWD}/../../:/app" \ -e "TZ=${TZ}" \ -v "/var/run/docker.sock:/var/run/docker.sock" \ --net="${NEKO_ROOMS_TRAEFIK_NETWORK}" \ From 5efafd7a3ab1c67237cf92fc0487af2f46c6a7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 20:55:25 +0200 Subject: [PATCH 11/32] move docker compose to traefik. --- install | 2 +- .env.example => traefik/.env.example | 0 .../docker-compose.http.yml | 0 docker-compose.yml => traefik/docker-compose.yml | 8 ++++---- 4 files changed, 5 insertions(+), 5 deletions(-) rename .env.example => traefik/.env.example (100%) rename docker-compose.http.yml => traefik/docker-compose.http.yml (100%) rename docker-compose.yml => traefik/docker-compose.yml (85%) diff --git a/install b/install index 74627d9..20e0d4a 100755 --- a/install +++ b/install @@ -239,7 +239,7 @@ echo "[Y] Downloading traefik config..." # Download docker compose file # -wget -O "./docker-compose.yml" "https://raw.githubusercontent.com/m1k1o/neko-rooms/master/docker-compose.yml" +wget -O "./docker-compose.yml" "https://raw.githubusercontent.com/m1k1o/neko-rooms/master/traefik/docker-compose.yml" # Pull neko images for NEKO_IMAGE in "${NEKO_IMAGES[@]}"; do diff --git a/.env.example b/traefik/.env.example similarity index 100% rename from .env.example rename to traefik/.env.example diff --git a/docker-compose.http.yml b/traefik/docker-compose.http.yml similarity index 100% rename from docker-compose.http.yml rename to traefik/docker-compose.http.yml diff --git a/docker-compose.yml b/traefik/docker-compose.yml similarity index 85% rename from docker-compose.yml rename to traefik/docker-compose.yml index 909e750..67fe6f1 100644 --- a/docker-compose.yml +++ b/traefik/docker-compose.yml @@ -21,10 +21,10 @@ services: - "443:443" volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - - "./traefik/traefik.yml:/etc/traefik/traefik.yml:ro" - - "./traefik/usersfile:/usersfile:ro" - - "./traefik/acme.json:/acme.json" - - "./traefik/config:/config" + - "./traefik.yml:/etc/traefik/traefik.yml:ro" + - "./usersfile:/usersfile:ro" + - "./acme.json:/acme.json" + - "./config:/config" neko-rooms: image: "m1k1o/neko-rooms:latest" From 2d67345601019a156ff50415c11f073070aa88e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 21:14:57 +0200 Subject: [PATCH 12/32] cleanup traefik readme. --- README.md | 56 ++---------------- docker-compose.yml | 24 ++++++++ traefik/README.md | 67 ++++++++++++++++++++++ install => traefik/install | 0 {docs => traefik}/nginx/docker-compose.yml | 0 {docs => traefik}/nginx/nginx.conf | 0 6 files changed, 97 insertions(+), 50 deletions(-) create mode 100644 docker-compose.yml create mode 100644 traefik/README.md rename install => traefik/install (100%) rename {docs => traefik}/nginx/docker-compose.yml (100%) rename {docs => traefik}/nginx/nginx.conf (100%) diff --git a/README.md b/README.md index 4e9240a..bdf1277 100644 --- a/README.md +++ b/README.md @@ -16,70 +16,25 @@ Simple room management system for [n.eko](https://github.com/m1k1o/neko). Self h n.eko -## Zero-knowledge installation +## Zero-knowledge installation (with HTTPS and Traefik) If you don't have any clue about docker and stuff but only want to have fun with friends in a shared browser, we got you covered! - Rent a VPS with public IP and OS Ubuntu. - Get a domain name pointing to your IP (you can even get some for free). - Run install script and follow instructions. +- Secure using HTTPs thanks to Let's Encrypt and Traefik. ```bash -wget -O neko-rooms.sh https://raw.githubusercontent.com/m1k1o/neko-rooms/master/install +wget -O neko-rooms.sh https://raw.githubusercontent.com/m1k1o/neko-rooms/master/traefik/install sudo bash neko-rooms.sh ``` ## How to start -You need to have installed `Docker` and `docker-compose`. You need to have a custom domain pointing to your server's IP. +If you want to use Traefik as reverse proxy, visit [installation guide for traefik as reverse proxy](./traefik/). -You can watch installation video provided by *Dr R1ck*: - -https://www.youtube.com/watch?v=cCmnw-pq0gA - -### Installation guide - -You only need `.env.example`, `docker-compose.yml` and `traefik/`. - -#### Do I need to use traefik? - -- Traefik needs to be used to forward traffic to the rooms. You can put nginx in front of it, but not replace it. -- See example configuration for [nginx](docs/nginx). - -You can use `docker-compose.http.yml` that will expose this service to `8080` or any port. Authentication is optional. Start it quickly with `docker-compose -f docker-compose.http.yml up -d`. - -### Step 1 - -Copy `.env.example` to `.env` and customize. - -```bash -cp .env.example .env -``` - -### Step 2 - -Create `usersfile` with your users: - -```bash -touch traefik/usersfile -``` - -And add as many users as you like: - -```bash -echo $(htpasswd -nb user password) >> traefik/usersfile -``` - -### Step 3 (HTTPS only) - -Create `acme.json` - -```bash -touch traefik/acme.json -chmod 600 traefik/acme.json -``` - -Update your email in `traefik/traefik.yml`. +Otherwise modify variables in `docker-compose.yml` and just run `docker-compose up -d`. ### Download images / update @@ -116,6 +71,7 @@ For more information visit [docs](./docs). - [x] add authentication provider for traefik - [x] allow specifying custom ENV variables - [x] allow mounting directories for persistent data + - [x] optionally remove Traefik as dependency - [ ] add upgrade button - [ ] auto pull images, that do not exist - [ ] add bearer token to for API diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cee1537 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.5" + +networks: + default: + attachable: true + name: "neko-rooms-net" + +services: + neko-rooms: + image: "m1k1o/neko-rooms:latest" + restart: "unless-stopped" + environment: + - "TZ=Europe/Vienna" + - "NEKO_ROOMS_MUX=true" + - "NEKO_ROOMS_EPR=59000-59049" + - "NEKO_ROOMS_NAT1TO1=127.0.0.1" # IP address of your server that is reachable from client + - "NEKO_ROOMS_INSTANCE_URL=http://127.0.0.1:8080/" # external URL + - "NEKO_ROOMS_INSTANCE_NETWORK=neko-rooms-net" + - "NEKO_ROOMS_TRAEFIK_ENABLED=false" + - "NEKO_ROOMS_PATH_PREFIX=/room/" + ports: + - "8080:8080" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" diff --git a/traefik/README.md b/traefik/README.md new file mode 100644 index 0000000..a4a6f45 --- /dev/null +++ b/traefik/README.md @@ -0,0 +1,67 @@ +# Installation guide for traefik as reverse proxy + +## Zero-knowledge installation + +If you don't have any clue about docker and stuff but only want to have fun with friends in a shared browser, we got you covered! + +- Rent a VPS with public IP and OS Ubuntu. +- Get a domain name pointing to your IP (you can even get some for free). +- Run install script and follow instructions. + +```bash +wget -O neko-rooms.sh https://raw.githubusercontent.com/m1k1o/neko-rooms/master/traefik/install +sudo bash neko-rooms.sh +``` + +## How to start + +You need to have installed `Docker` and `docker-compose`. You need to have a custom domain pointing to your server's IP. + +You can watch installation video provided by *Dr R1ck*: + +https://www.youtube.com/watch?v=cCmnw-pq0gA + +### Installation guide + +You only need `.env.example`, `docker-compose.yml` and `traefik/`. + +#### Do I need to use traefik? + +- This project started with Traefik as a needed dependency. That, however, changed. Traefik must not be used but the original setup can still be used. +- Traefik is used to forward traffic to the rooms. You can put nginx in front of it. +- See example configuration for [nginx](./nginx). + +You can use `docker-compose.http.yml` that will expose this service to `8080` or any port. Authentication is optional. Start it quickly with `docker-compose -f docker-compose.http.yml up -d`. + +### Step 1 + +Copy `.env.example` to `.env` and customize. + +```bash +cp .env.example .env +``` + +### Step 2 + +Create `usersfile` with your users: + +```bash +touch usersfile +``` + +And add as many users as you like: + +```bash +echo $(htpasswd -nb user password) >> usersfile +``` + +### Step 3 (HTTPS only) + +Create `acme.json` + +```bash +touch acme.json +chmod 600 acme.json +``` + +Update your email in `traefik.yml`. diff --git a/install b/traefik/install similarity index 100% rename from install rename to traefik/install diff --git a/docs/nginx/docker-compose.yml b/traefik/nginx/docker-compose.yml similarity index 100% rename from docs/nginx/docker-compose.yml rename to traefik/nginx/docker-compose.yml diff --git a/docs/nginx/nginx.conf b/traefik/nginx/nginx.conf similarity index 100% rename from docs/nginx/nginx.conf rename to traefik/nginx/nginx.conf From 48f7d94b19e7b6bcf0bca06178e964a2a4bb6006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 23:15:46 +0200 Subject: [PATCH 13/32] default path prefix /. --- internal/config/room.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/room.go b/internal/config/room.go index 730aa9c..60ac32e 100644 --- a/internal/config/room.go +++ b/internal/config/room.go @@ -83,7 +83,7 @@ func (Room) Init(cmd *cobra.Command) error { return err } - cmd.PersistentFlags().String("path_prefix", "", "path prefix that is added to every room path") + cmd.PersistentFlags().String("path_prefix", "/", "path prefix that is added to every room path (starts with /)") if err := viper.BindPFlag("path_prefix", cmd.PersistentFlags().Lookup("path_prefix")); err != nil { return err } From 5abcf4359acbc596084207f0006e575cb2debb02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 31 Jul 2022 23:26:00 +0200 Subject: [PATCH 14/32] admin path prefix & password. --- Dockerfile | 2 +- dev/start | 1 + dev/traefik/start | 1 - internal/config/server.go | 48 ++++++++++++++++++---------- internal/server/manager.go | 65 +++++++++++++++++++++++++++----------- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/Dockerfile b/Dockerfile index f6ba64d..664d417 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ COPY --from=frontend /src/dist/ /var/www ENV DOCKER_API_VERSION=1.39 ENV NEKO_ROOMS_BIND=:8080 -ENV NEKO_ROOMS_STATIC=/var/www +ENV NEKO_ROOMS_ADMIN_STATIC=/var/www EXPOSE 8080 diff --git a/dev/start b/dev/start index fed2a65..5588ddb 100755 --- a/dev/start +++ b/dev/start @@ -53,6 +53,7 @@ docker run --rm -it \ -e "NEKO_ROOMS_STORAGE_EXTERNAL=`realpath ${DATA_PATH}`" \ -e "NEKO_ROOMS_MOUNTS_WHITELIST=`realpath ${EXTERNAL_PATH}`" \ -e "NEKO_ROOMS_PATH_PREFIX=/room/" \ + -e "NEKO_ROOMS_ADMIN_PATH_PREFIX=/" \ -e "NEKO_ROOMS_TRAEFIK_ENABLED=false" \ -e 'DOCKER_API_VERSION=1.39' \ -v "/var/run/docker.sock:/var/run/docker.sock" \ diff --git a/dev/traefik/start b/dev/traefik/start index 7371258..3106674 100755 --- a/dev/traefik/start +++ b/dev/traefik/start @@ -24,7 +24,6 @@ docker run --rm -it \ -e "NEKO_ROOMS_MUX=true" \ -e "NEKO_ROOMS_NAT1TO1=${NEKO_ROOMS_NAT1TO1}" \ -e "NEKO_ROOMS_PATH_PREFIX=/room/" \ - -e "NEKO_ROOMS_ADMIN_PATH_PREFIX=/" \ -e "NEKO_ROOMS_STORAGE_INTERNAL=/data" \ -e "NEKO_ROOMS_STORAGE_EXTERNAL=`realpath ${DATA_PATH}`" \ -e "NEKO_ROOMS_MOUNTS_WHITELIST=`realpath ${EXTERNAL_PATH}`" \ diff --git a/internal/config/server.go b/internal/config/server.go index 1f119f9..d829adc 100644 --- a/internal/config/server.go +++ b/internal/config/server.go @@ -5,15 +5,20 @@ import ( "github.com/spf13/viper" ) +type Admin struct { + Static string + Password string + PathPrefix string +} + type Server struct { - Cert string - Key string - Bind string - Static string - Proxy bool - PProf bool - - AdminPathPrefix string + Cert string + Key string + Bind string + Proxy bool + PProf bool + + Admin Admin } func (Server) Init(cmd *cobra.Command) error { @@ -32,11 +37,6 @@ func (Server) Init(cmd *cobra.Command) error { return err } - cmd.PersistentFlags().String("static", "./www", "path to neko_rooms client files to serve") - if err := viper.BindPFlag("static", cmd.PersistentFlags().Lookup("static")); err != nil { - return err - } - cmd.PersistentFlags().Bool("proxy", false, "trust reverse proxy headers") if err := viper.BindPFlag("proxy", cmd.PersistentFlags().Lookup("proxy")); err != nil { return err @@ -47,8 +47,21 @@ func (Server) Init(cmd *cobra.Command) error { return err } - cmd.PersistentFlags().String("admin_path_prefix", "/", "set custom path prefix for admin") - if err := viper.BindPFlag("admin_path_prefix", cmd.PersistentFlags().Lookup("admin_path_prefix")); err != nil { + // Admin + + cmd.PersistentFlags().String("admin.static", "", "path to neko_rooms admin client files to serve") + if err := viper.BindPFlag("admin.static", cmd.PersistentFlags().Lookup("admin.static")); err != nil { + return err + } + + cmd.PersistentFlags().String("admin.password", "", "admin password") + if err := viper.BindPFlag("admin.password", cmd.PersistentFlags().Lookup("admin.password")); err != nil { + return err + } + + // TODO: Default in v2 will be '/admin'. + cmd.PersistentFlags().String("admin.path_prefix", "", "set custom path prefix for admin") + if err := viper.BindPFlag("admin.path_prefix", cmd.PersistentFlags().Lookup("admin.path_prefix")); err != nil { return err } @@ -59,9 +72,10 @@ func (s *Server) Set() { s.Cert = viper.GetString("cert") s.Key = viper.GetString("key") s.Bind = viper.GetString("bind") - s.Static = viper.GetString("static") s.Proxy = viper.GetBool("proxy") s.PProf = viper.GetBool("pprof") - s.AdminPathPrefix = viper.GetString("admin_path_prefix") + s.Admin.Static = viper.GetString("admin.static") + s.Admin.Password = viper.GetString("admin.password") + s.Admin.PathPrefix = viper.GetString("admin.path_prefix") } diff --git a/internal/server/manager.go b/internal/server/manager.go index 50b74f2..e073e0b 100644 --- a/internal/server/manager.go +++ b/internal/server/manager.go @@ -3,7 +3,8 @@ package server import ( "context" "net/http" - "os" + "path" + "strings" "time" "github.com/go-chi/chi" @@ -54,24 +55,10 @@ func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Se logger.Info().Msgf("with pprof endpoint at %s", pprofPath) } - // admin page - router.Route(config.AdminPathPrefix, func(r chi.Router) { - r.Route("/api", ApiManager.Mount) - - // serve static files - if config.Static != "" { - fs := http.FileServer(http.Dir(config.Static)) - r.Get("/*", func(w http.ResponseWriter, r *http.Request) { - if _, err := os.Stat(config.Static + r.URL.Path); !os.IsNotExist(err) { - fs.ServeHTTP(w, r) - } else { - http.NotFound(w, r) - } - }) - } - }) - + // // rooms page + // + router.Route(roomConfig.PathPrefix, func(r chi.Router) { if !roomConfig.Traefik.Enabled { r.Handle("/*", proxyHandler) @@ -82,6 +69,48 @@ func New(ApiManager types.ApiManager, roomConfig *config.Room, config *config.Se } }) + // + // admin page + // + + // in v1 default location was at / with traefik overriding + // the actual room address. in order to keep this setting + // we set new default path prefix only without traefik + if !roomConfig.Traefik.Enabled && config.Admin.PathPrefix == "" { + config.Admin.PathPrefix = "/admin" + } + + router.Group(func(r chi.Router) { + // handle authorization + if config.Admin.Password != "" { + r.Use(middleware.BasicAuth("neko-rooms admin", map[string]string{ + "admin": config.Admin.Password, + })) + } + + // bind API + apiPath := path.Join("/", config.Admin.PathPrefix, "/api") + router.Route(apiPath, ApiManager.Mount) + + // serve static files + if config.Admin.Static != "" { + prefix := config.Admin.PathPrefix + dir := http.Dir(config.Admin.Static) + + fs := http.FileServer(dir) + fs = http.StripPrefix(prefix, fs) + + router.Handle(prefix+"/", fs) + } + }) + + // redirect / to admin path prefix + if config.Admin.PathPrefix != "/" { + router.Get("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, strings.TrimPrefix(config.Admin.PathPrefix, "/"), http.StatusTemporaryRedirect) + }) + } + // we could use custom 404 router.NotFound(http.NotFound) From d779dc6f4af62660d506c8e81c00324ae46f288e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 1 Aug 2022 20:17:56 +0200 Subject: [PATCH 15/32] max connections 0 when using mux. --- OpenApi.yml | 2 + client/src/components/RoomInfo.vue | 12 +++-- client/src/components/RoomsCreate.vue | 2 +- client/src/components/RoomsList.vue | 4 +- client/src/views/Home.vue | 2 +- internal/room/containers.go | 10 +++- internal/room/labels.go | 70 +++++++++++++++++++-------- internal/room/manager.go | 5 ++ internal/types/room.go | 4 +- 9 files changed, 77 insertions(+), 34 deletions(-) diff --git a/OpenApi.yml b/OpenApi.yml index 330e120..42f3ea4 100644 --- a/OpenApi.yml +++ b/OpenApi.yml @@ -307,6 +307,7 @@ components: max_connections: type: number example: 10 + description: 0 when using mux running: type: boolean example: true @@ -361,6 +362,7 @@ components: max_connections: type: number default: 10 + description: 0 when using mux user_pass: type: string example: neko diff --git a/client/src/components/RoomInfo.vue b/client/src/components/RoomInfo.vue index d9c07cf..ee9109e 100644 --- a/client/src/components/RoomInfo.vue +++ b/client/src/components/RoomInfo.vue @@ -25,10 +25,10 @@ :rotate="270" :size="100" :width="15" - :value="usesMux ? 100 : ((stats.connections / settings.max_connections) * 100)" + :value="settings.max_connections == 0 ? 100 : ((stats.connections / settings.max_connections) * 100)" color="blue" > - {{ stats.connections }} + {{ stats.connections }} @@ -99,7 +99,10 @@ Invite link for admins - Max connections {{ settings.max_connections }} + Max connections + + not limited because uses mux + Control protection {{ settings.control_protection }} Implicit Control {{ settings.implicit_control }} @@ -275,8 +278,7 @@ export default class RoomInfo extends Vue { } get usesMux() { - // old rooms might not use mux despite enabled server wide, simple check for that - return this.$store.state.roomsConfig.uses_mux && this.settings?.max_connections == 1 + return this.$store.state.roomsConfig.uses_mux } get allBrowserExtensions() { diff --git a/client/src/components/RoomsCreate.vue b/client/src/components/RoomsCreate.vue index 8546f3f..b78bd29 100644 --- a/client/src/components/RoomsCreate.vue +++ b/client/src/components/RoomsCreate.vue @@ -604,7 +604,7 @@ export default class RoomsCreate extends Vue { // eslint-disable-next-line admin_pass: this.data.admin_pass || randomPassword(), // eslint-disable-next-line - max_connections: Number(this.data.max_connections), // not needed when uses mux + max_connections: Number(this.data.max_connections), // ignored when uses mux // eslint-disable-next-line control_protection: Boolean(this.data.control_protection), // eslint-disable-next-line diff --git a/client/src/components/RoomsList.vue b/client/src/components/RoomsList.vue index 6f92daa..95afd82 100644 --- a/client/src/components/RoomsList.vue +++ b/client/src/components/RoomsList.vue @@ -33,8 +33,8 @@