mirror of
https://github.com/owncast/owncast.git
synced 2024-11-21 20:28:15 +03:00
feat(api): add server-side caching for requests that could benefit (#3463)
* feat(api): add server-side caching for requests that could benefit for them * fix(tests): do not cache responses while in tests * fix: remove commented out leftover code * chore(deps): update dependency html-webpack-plugin to v5.5.4 * Bundle embedded web app * fix: remove caching for web app assets under test * chore(tests): re-enable temporarily disabled test * chore(deps): update dependency typescript to v5.3.3 * Bundle embedded web app * chore(deps): update dependency npm to v10.2.5 * Bundle embedded web app --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Owncast <owncast@owncast.online>
This commit is contained in:
parent
b6efe49086
commit
2217f0614a
21 changed files with 316 additions and 119 deletions
|
@ -30,4 +30,8 @@ var (
|
|||
|
||||
// PublicFilesPath is the optional directory for hosting public files.
|
||||
PublicFilesPath = filepath.Join(DataDirectory, "public")
|
||||
|
||||
// DisableResponseCaching will disable caching of API and resource
|
||||
// responses. Disable this feature to turn off the optimizations.
|
||||
DisableResponseCaching = false
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/owncast/owncast/config"
|
||||
"github.com/owncast/owncast/core"
|
||||
|
@ -13,8 +14,19 @@ import (
|
|||
"github.com/owncast/owncast/models"
|
||||
"github.com/owncast/owncast/router/middleware"
|
||||
"github.com/owncast/owncast/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
cache "github.com/victorspringer/http-cache"
|
||||
"github.com/victorspringer/http-cache/adapter/memory"
|
||||
)
|
||||
|
||||
type FileServerHandler struct {
|
||||
HLSPath string
|
||||
}
|
||||
|
||||
func (fsh *FileServerHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(rw, r, fsh.HLSPath)
|
||||
}
|
||||
|
||||
// HandleHLSRequest will manage all requests to HLS content.
|
||||
func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// Sanity check to limit requests to HLS file types.
|
||||
|
@ -23,6 +35,26 @@ func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
responseCache, err := memory.NewAdapter(
|
||||
memory.AdapterWithAlgorithm(memory.LRU),
|
||||
memory.AdapterWithCapacity(20),
|
||||
memory.AdapterWithStorageCapacity(209_715_200),
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn("unable to create web cache", err)
|
||||
}
|
||||
|
||||
// Since HLS segments cannot be changed once they're rendered, we can cache
|
||||
// individual segments for a long time.
|
||||
longTermHLSSegmentCache, err := cache.NewClient(
|
||||
cache.ClientWithAdapter(responseCache),
|
||||
cache.ClientWithTTL(30*time.Second),
|
||||
cache.ClientWithExpiresHeader(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn("unable to create web cache client", err)
|
||||
}
|
||||
|
||||
requestedPath := r.URL.Path
|
||||
relativePath := strings.Replace(requestedPath, "/hls/", "", 1)
|
||||
fullPath := filepath.Join(config.HLSStoragePath, relativePath)
|
||||
|
@ -48,6 +80,10 @@ func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
|
|||
} else {
|
||||
cacheTime := utils.GetCacheDurationSecondsForPath(relativePath)
|
||||
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheTime))
|
||||
|
||||
fileServer := &FileServerHandler{HLSPath: fullPath}
|
||||
longTermHLSSegmentCache.Middleware(fileServer).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.EnableCors(w)
|
||||
|
|
1
go.mod
1
go.mod
|
@ -69,6 +69,7 @@ require (
|
|||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.11.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/victorspringer/http-cache v0.0.0-20231006141456-6446fe59efba // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
2
go.sum
2
go.sum
|
@ -138,6 +138,8 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
|
|||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g=
|
||||
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/victorspringer/http-cache v0.0.0-20231006141456-6446fe59efba h1:+oqDKQIOdkkvro1psUKtI4oH9WBeKkGY2S8h9/lo288=
|
||||
github.com/victorspringer/http-cache v0.0.0-20231006141456-6446fe59efba/go.mod h1:D1AD6nlXv7HkIfTVd8ZWK1KQEiXYNy/LbLkx8H9tIQw=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||
|
|
27
main.go
27
main.go
|
@ -17,17 +17,18 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
dbFile = flag.String("database", "", "Path to the database file.")
|
||||
logDirectory = flag.String("logdir", "", "Directory where logs will be written to")
|
||||
backupDirectory = flag.String("backupdir", "", "Directory where backups will be written to")
|
||||
enableDebugOptions = flag.Bool("enableDebugFeatures", false, "Enable additional debugging options.")
|
||||
enableVerboseLogging = flag.Bool("enableVerboseLogging", false, "Enable additional logging.")
|
||||
restoreDatabaseFile = flag.String("restoreDatabase", "", "Restore an Owncast database backup")
|
||||
newAdminPassword = flag.String("adminpassword", "", "Set your admin password")
|
||||
newStreamKey = flag.String("streamkey", "", "Set a temporary stream key for this session")
|
||||
webServerPortOverride = flag.String("webserverport", "", "Force the web server to listen on a specific port")
|
||||
webServerIPOverride = flag.String("webserverip", "", "Force web server to listen on this IP address")
|
||||
rtmpPortOverride = flag.Int("rtmpport", 0, "Set listen port for the RTMP server")
|
||||
dbFile = flag.String("database", "", "Path to the database file.")
|
||||
logDirectory = flag.String("logdir", "", "Directory where logs will be written to")
|
||||
backupDirectory = flag.String("backupdir", "", "Directory where backups will be written to")
|
||||
enableDebugOptions = flag.Bool("enableDebugFeatures", false, "Enable additional debugging options.")
|
||||
enableVerboseLogging = flag.Bool("enableVerboseLogging", false, "Enable additional logging.")
|
||||
restoreDatabaseFile = flag.String("restoreDatabase", "", "Restore an Owncast database backup")
|
||||
newAdminPassword = flag.String("adminpassword", "", "Set your admin password")
|
||||
newStreamKey = flag.String("streamkey", "", "Set a temporary stream key for this session")
|
||||
webServerPortOverride = flag.String("webserverport", "", "Force the web server to listen on a specific port")
|
||||
webServerIPOverride = flag.String("webserverip", "", "Force web server to listen on this IP address")
|
||||
rtmpPortOverride = flag.Int("rtmpport", 0, "Set listen port for the RTMP server")
|
||||
disableResponseCaching = flag.Bool("disableResponseCaching", false, "Do not optimize performance by caching of web responses")
|
||||
)
|
||||
|
||||
// nolint:cyclop
|
||||
|
@ -42,6 +43,10 @@ func main() {
|
|||
config.BackupDirectory = *backupDirectory
|
||||
}
|
||||
|
||||
if *disableResponseCaching {
|
||||
config.DisableResponseCaching = *disableResponseCaching
|
||||
}
|
||||
|
||||
// Create the data directory if needed
|
||||
if !utils.DoesFileExists("data") {
|
||||
if err := os.Mkdir("./data", 0o700); err != nil {
|
||||
|
|
109
router/router.go
109
router/router.go
|
@ -24,52 +24,131 @@ import (
|
|||
"github.com/owncast/owncast/router/middleware"
|
||||
"github.com/owncast/owncast/utils"
|
||||
"github.com/owncast/owncast/yp"
|
||||
|
||||
cache "github.com/victorspringer/http-cache"
|
||||
"github.com/victorspringer/http-cache/adapter/memory"
|
||||
)
|
||||
|
||||
// Start starts the router for the http, ws, and rtmp.
|
||||
func Start() error {
|
||||
// Setup a web response cache
|
||||
enableCache := !config.DisableResponseCaching
|
||||
|
||||
responseCache, err := memory.NewAdapter(
|
||||
memory.AdapterWithAlgorithm(memory.LRU),
|
||||
memory.AdapterWithCapacity(50),
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn("unable to create web cache", err)
|
||||
}
|
||||
|
||||
superShortCacheClient, err := cache.NewClient(
|
||||
cache.ClientWithAdapter(responseCache),
|
||||
cache.ClientWithTTL(3*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn("unable to create web cache client", err)
|
||||
}
|
||||
reasonableDurationCacheClient, err := cache.NewClient(
|
||||
cache.ClientWithAdapter(responseCache),
|
||||
cache.ClientWithTTL(8*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn("unable to create web cache client", err)
|
||||
}
|
||||
longerDurationCacheClient, err := cache.NewClient(
|
||||
cache.ClientWithAdapter(responseCache),
|
||||
cache.ClientWithTTL(3*time.Minute),
|
||||
)
|
||||
if err != nil {
|
||||
log.Warn("unable to create web cache client", err)
|
||||
}
|
||||
// The primary web app.
|
||||
http.HandleFunc("/", controllers.IndexHandler)
|
||||
if enableCache {
|
||||
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.IndexHandler)).ServeHTTP(rw, r)
|
||||
})
|
||||
} else {
|
||||
http.HandleFunc("/", controllers.IndexHandler)
|
||||
}
|
||||
|
||||
// The admin web app.
|
||||
http.HandleFunc("/admin/", middleware.RequireAdminAuth(controllers.IndexHandler))
|
||||
|
||||
// Images
|
||||
http.HandleFunc("/thumbnail.jpg", controllers.GetThumbnail)
|
||||
http.HandleFunc("/preview.gif", controllers.GetPreview)
|
||||
http.HandleFunc("/logo", controllers.GetLogo)
|
||||
http.HandleFunc("/thumbnail.jpg", func(rw http.ResponseWriter, r *http.Request) {
|
||||
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetThumbnail)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
http.HandleFunc("/preview.gif", func(rw http.ResponseWriter, r *http.Request) {
|
||||
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetPreview)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
http.HandleFunc("/logo", func(rw http.ResponseWriter, r *http.Request) {
|
||||
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetLogo)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// Custom Javascript
|
||||
http.HandleFunc("/customjavascript", controllers.ServeCustomJavascript)
|
||||
http.HandleFunc("/customjavascript", func(rw http.ResponseWriter, r *http.Request) {
|
||||
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.ServeCustomJavascript)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// Return a single emoji image.
|
||||
http.HandleFunc(config.EmojiDir, controllers.GetCustomEmojiImage)
|
||||
http.HandleFunc(config.EmojiDir, func(rw http.ResponseWriter, r *http.Request) {
|
||||
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetCustomEmojiImage)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// return the logo
|
||||
|
||||
// return a logo that's compatible with external social networks
|
||||
http.HandleFunc("/logo/external", controllers.GetCompatibleLogo)
|
||||
http.HandleFunc("/logo/external", func(rw http.ResponseWriter, r *http.Request) {
|
||||
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetCompatibleLogo)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// robots.txt
|
||||
http.HandleFunc("/robots.txt", controllers.GetRobotsDotTxt)
|
||||
http.HandleFunc("/robots.txt", func(rw http.ResponseWriter, r *http.Request) {
|
||||
longerDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetRobotsDotTxt)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// status of the system
|
||||
http.HandleFunc("/api/status", controllers.GetStatus)
|
||||
if enableCache {
|
||||
http.HandleFunc("/api/status", func(rw http.ResponseWriter, r *http.Request) {
|
||||
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetStatus)).ServeHTTP(rw, r)
|
||||
})
|
||||
} else {
|
||||
http.HandleFunc("/api/status", controllers.GetStatus)
|
||||
}
|
||||
|
||||
// custom emoji supported in the chat
|
||||
http.HandleFunc("/api/emoji", controllers.GetCustomEmojiList)
|
||||
http.HandleFunc("/api/emoji", func(rw http.ResponseWriter, r *http.Request) {
|
||||
reasonableDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetCustomEmojiList)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// chat rest api
|
||||
http.HandleFunc("/api/chat", middleware.RequireUserAccessToken(controllers.GetChatMessages))
|
||||
if enableCache {
|
||||
http.HandleFunc("/api/chat", func(rw http.ResponseWriter, r *http.Request) {
|
||||
superShortCacheClient.Middleware(middleware.RequireUserAccessToken(controllers.GetChatMessages))
|
||||
})
|
||||
} else {
|
||||
http.HandleFunc("/api/chat", middleware.RequireUserAccessToken(controllers.GetChatMessages))
|
||||
}
|
||||
|
||||
// web config api
|
||||
http.HandleFunc("/api/config", controllers.GetWebConfig)
|
||||
if enableCache {
|
||||
http.HandleFunc("/api/config", func(rw http.ResponseWriter, r *http.Request) {
|
||||
superShortCacheClient.Middleware(http.HandlerFunc(controllers.GetWebConfig)).ServeHTTP(rw, r)
|
||||
})
|
||||
} else {
|
||||
http.HandleFunc("/api/config", controllers.GetWebConfig)
|
||||
}
|
||||
|
||||
// return the YP protocol data
|
||||
http.HandleFunc("/api/yp", yp.GetYPResponse)
|
||||
|
||||
// list of all social platforms
|
||||
http.HandleFunc("/api/socialplatforms", controllers.GetAllSocialPlatforms)
|
||||
http.HandleFunc("/api/socialplatforms", func(rw http.ResponseWriter, r *http.Request) {
|
||||
reasonableDurationCacheClient.Middleware(http.HandlerFunc(controllers.GetAllSocialPlatforms)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// return the list of video variants available
|
||||
http.HandleFunc("/api/video/variants", controllers.GetVideoStreamOutputVariants)
|
||||
|
@ -84,7 +163,9 @@ func Start() error {
|
|||
http.HandleFunc("/api/remotefollow", controllers.RemoteFollow)
|
||||
|
||||
// return followers
|
||||
http.HandleFunc("/api/followers", middleware.HandlePagination(controllers.GetFollowers))
|
||||
http.HandleFunc("/api/followers", func(rw http.ResponseWriter, r *http.Request) {
|
||||
reasonableDurationCacheClient.Middleware(middleware.HandlePagination(controllers.GetFollowers)).ServeHTTP(rw, r)
|
||||
})
|
||||
|
||||
// save client video playback metrics
|
||||
http.HandleFunc("/api/metrics/playback", controllers.ReportPlaybackMetrics)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB();
|
1
static/web/_next/static/chunks/4281-f024035bb909af4e.js
vendored
Normal file
1
static/web/_next/static/chunks/4281-f024035bb909af4e.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/web/_next/static/chunks/5056-b14d7a3d2aee94c3.js
vendored
Normal file
1
static/web/_next/static/chunks/5056-b14d7a3d2aee94c3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
static/web/_next/static/chunks/5283-23ec4c27947dae66.js
vendored
Normal file
11
static/web/_next/static/chunks/5283-23ec4c27947dae66.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/web/_next/static/chunks/5584.08b6806ea3012a43.js
vendored
Normal file
1
static/web/_next/static/chunks/5584.08b6806ea3012a43.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5584],{15584:function(e,t,n){n.r(t);var r,a,s,o,i,l=n(67294);function _extends(){return(_extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}t.default=function(e){return l.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",width:500,height:500,viewBox:"0 0 132.292 132.292"},e),r||(r=l.createElement("linearGradient",{id:"like_svg__a",x1:432.851,x2:464.644,y1:49.977,y2:49.977,gradientUnits:"userSpaceOnUse"},l.createElement("stop",{offset:0,stopColor:"#2087e2"}),l.createElement("stop",{offset:1,stopColor:"#b63fff"}))),a||(a=l.createElement("path",{fill:"url(#like_svg__a)",d:"M438.672 34.08h20.151a5.82 5.82 45 0 1 5.82 5.821v20.151a5.82 5.82 135 0 1-5.82 5.821h-20.15a5.82 5.82 45 0 1-5.822-5.82V39.9a5.82 5.82 135 0 1 5.821-5.82z",transform:"matrix(4.1611 0 0 4.1611 -1801.14 -141.813)"})),s||(s=l.createElement("path",{fill:"#853dd0",d:"M106.243 25.198 110 33.435l5.378 24.12-20.557 29.696-28.676 20.66-35.66-24.468 49.536 48.849h28.048a24.221 24.221 0 0 0 24.222-24.222V44.165z",opacity:.75})),o||(o=l.createElement("path",{fill:"#8392ee",d:"M51.275 39.14s-36.386-7.356-17.999 25.83c13.869 25.032 29.59 23.091 29.59 23.091S47.512 65.822 51.275 39.14"})),i||(i=l.createElement("path",{fill:"none",stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:7.865,d:"M674.216 70.254c-21.056-22.863-45.943 2.215-45.943 2.215s-24.888-25.078-45.943-2.216c-21.056 22.863 16.89 64.133 45.943 78.023 29.053-13.89 66.998-55.16 45.943-78.022",transform:"matrix(.95455 0 0 .95455 -533.57 -33.626)"})))}}}]);
|
19
static/web/_next/static/chunks/6017-bb9b0312a26c35dd.js
vendored
Normal file
19
static/web/_next/static/chunks/6017-bb9b0312a26c35dd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/web/_next/static/chunks/7268.461abbd8430d83c0.js
vendored
Normal file
1
static/web/_next/static/chunks/7268.461abbd8430d83c0.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7268],{97268:function(e,t,r){r.r(t);var a,n,s,o,l=r(67294);function _extends(){return(_extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e}).apply(this,arguments)}t.default=function(e){return l.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",width:500,height:500,viewBox:"0 0 132.292 132.292"},e),a||(a=l.createElement("linearGradient",{id:"repost_svg__a",x1:432.851,x2:464.644,y1:49.977,y2:49.977,gradientUnits:"userSpaceOnUse"},l.createElement("stop",{offset:0,stopColor:"#2087e2"}),l.createElement("stop",{offset:1,stopColor:"#b63fff"}))),n||(n=l.createElement("path",{fill:"url(#repost_svg__a)",d:"M438.672 34.08h20.151a5.82 5.82 45 0 1 5.82 5.821v20.151a5.82 5.82 135 0 1-5.82 5.821h-20.15a5.82 5.82 45 0 1-5.822-5.82V39.9a5.82 5.82 135 0 1 5.821-5.82z",transform:"matrix(4.16112 0 0 4.1611 -1801.146 -141.813)"})),s||(s=l.createElement("path",{fill:"#7f40cf",d:"m103.028 50.073-.794 41.033-10.18 12.882-49.412 3.477 26.027 24.827h39.4c13.378 0 24.223-10.845 24.222-24.222V68.265l-9.86-12.31z",opacity:.75})),o||(o=l.createElement("g",{fill:"none",stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:7.865},l.createElement("path",{d:"M741.453 94.965h-41.418a14.744 14.744 0 0 0-14.777 14.777v41.418m14.777 14.777h41.418c8.187 0 14.777-6.59 14.777-14.777v-41.418",transform:"translate(-696.642 -71.915)scale(1.05833)"}),l.createElement("path",{d:"m670.076 143.371 15.182 13.79 15.5-13.6",transform:"translate(-696.642 -71.915)scale(1.05833)"}),l.createElement("path",{d:"m670.076 143.371 15.182 13.79 15.5-13.6",transform:"rotate(180 414.466 105.278)scale(1.05833)"}))))}}}]);
|
1
static/web/_next/static/chunks/7521-9644c656878abfe6.js
vendored
Normal file
1
static/web/_next/static/chunks/7521-9644c656878abfe6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/web/_next/static/chunks/9069.663a9f4ff16078e0.js
vendored
Normal file
1
static/web/_next/static/chunks/9069.663a9f4ff16078e0.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9069],{49069:function(e,t,r){r.r(t);var a,n,o,l,s=r(67294);function _extends(){return(_extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e}).apply(this,arguments)}t.default=function(e){return s.createElement("svg",_extends({xmlns:"http://www.w3.org/2000/svg",width:500,height:500,viewBox:"0 0 132.292 132.292"},e),a||(a=s.createElement("linearGradient",{id:"follow_svg__a",x1:432.851,x2:464.644,y1:49.977,y2:49.977,gradientUnits:"userSpaceOnUse"},s.createElement("stop",{offset:0,stopColor:"#2087e2"}),s.createElement("stop",{offset:1,stopColor:"#b63fff"}))),n||(n=s.createElement("path",{fill:"url(#follow_svg__a)",d:"M438.672 34.08h20.151a5.82 5.82 45 0 1 5.82 5.821v20.151a5.82 5.82 135 0 1-5.82 5.821h-20.15a5.82 5.82 45 0 1-5.822-5.82V39.9a5.82 5.82 135 0 1 5.821-5.82z",transform:"matrix(4.16112 0 0 4.1611 -1801.146 -141.813)"})),o||(o=s.createElement("path",{fill:"#8842da",d:"m99.29 73.002-1.238 22.769-22.423.995 25.259 35.526h7.183c13.377-.001 24.22-10.845 24.22-24.222V89.683z",opacity:.85})),l||(l=s.createElement("g",{stroke:"#fff",strokeLinecap:"round",strokeLinejoin:"round"},s.createElement("circle",{cx:876.218,cy:118.03,r:21.554,fill:"none",strokeWidth:8.788,transform:"matrix(.90817 0 0 .9124 -737.017 -65.428)"}),s.createElement("path",{fill:"none",strokeWidth:6.641,d:"M845.107 163.996c0-16.543 13.41-29.953 29.953-29.953a29.953 29.953 0 0 1 19.632 7.331",transform:"matrix(1.14743 0 0 1.26483 -944.188 -103.004)"}),s.createElement("g",{fill:"#fff",strokeWidth:7.559},s.createElement("path",{d:"m881.641 159.874 34.92.28",transform:"translate(-853.609 -74.031)scale(1.05833)"}),s.createElement("path",{d:"m881.641 159.874 34.92.28",transform:"rotate(90 561.76 -294.47)scale(1.05833)"})))))}}}]);
|
6
static/web/_next/static/chunks/ee8b1517-4bb4890d4ff38215.js
vendored
Normal file
6
static/web/_next/static/chunks/ee8b1517-4bb4890d4ff38215.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/web/_next/static/chunks/pages/admin/upgrade-ba6e73bb555cba17.js
vendored
Normal file
1
static/web/_next/static/chunks/pages/admin/upgrade-ba6e73bb555cba17.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9262],{70918:function(e,t,a){(window.__NEXT_P=window.__NEXT_P||[]).push(["/admin/upgrade",function(){return a(7494)}])},7494:function(e,t,a){"use strict";a.r(t);var n=a(85893),s=a(67294),l=a(59899),r=a(2307),d=a(53740),u=a(92863),i=a(15578);let{Title:o}=d.default,AssetTable=e=>{let t=Object.values(e);return(0,n.jsx)(r.Z,{dataSource:t,columns:[{title:"Name",dataIndex:"name",key:"name",render:(e,t)=>(0,n.jsx)("a",{href:t.browser_download_url,children:e})},{title:"Size",dataIndex:"size",key:"size",render:e=>"".concat((e/1024/1024).toFixed(2)," MB")}],rowKey:e=>e.id,size:"large",pagination:!1})},Logs=()=>{let[e,t]=(0,s.useState)({html_url:"",name:"",created_at:null,body:"",assets:[]}),getRelease=async()=>{try{let e=await (0,u.Kt)();t(e)}catch(e){console.log("==== error",e)}};return((0,s.useEffect)(()=>{getRelease()},[]),e)?(0,n.jsxs)("div",{className:"upgrade-page",children:[(0,n.jsx)(o,{level:2,children:(0,n.jsx)("a",{href:e.html_url,children:e.name})}),(0,n.jsx)(o,{level:5,children:new Date(e.created_at).toDateString()}),(0,n.jsx)(l.U,{children:e.body}),(0,n.jsx)("h3",{children:"Downloads"}),(0,n.jsx)(AssetTable,{...e.assets})]}):null};Logs.getLayout=function(e){return(0,n.jsx)(i.l,{page:e})},t.default=Logs}},function(e){e.O(0,[5596,1130,4104,9403,1024,3942,971,6697,1664,1749,1700,2122,7752,5891,2891,4749,6627,8966,7521,5578,9774,2888,179],function(){return e(e.s=70918)}),_N_E=e.O()}]);
|
1
static/web/_next/static/chunks/webpack-17c574c3c0f8700e.js
vendored
Normal file
1
static/web/_next/static/chunks/webpack-17c574c3c0f8700e.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -9,9 +9,9 @@ request = request('http://127.0.0.1:8080');
|
|||
var ajv = new Ajv();
|
||||
var nodeInfoSchema = jsonfile.readFileSync('schema/nodeinfo_2.0.json');
|
||||
|
||||
const serverName = 'owncast.server.test'
|
||||
const serverURL = 'http://' + serverName
|
||||
const fediUsername = 'streamer'
|
||||
const serverName = 'owncast.server.test';
|
||||
const serverURL = 'http://' + serverName;
|
||||
const fediUsername = 'streamer';
|
||||
|
||||
test('disable federation', async (done) => {
|
||||
const res = await sendAdminRequest('config/federation/enable', false);
|
||||
|
@ -59,10 +59,7 @@ test('verify responses of /federation/ when federation is disabled', async (done
|
|||
});
|
||||
|
||||
test('set required parameters and enable federation', async (done) => {
|
||||
const res1 = await sendAdminRequest(
|
||||
'config/serverurl',
|
||||
serverURL
|
||||
);
|
||||
const res1 = await sendAdminRequest('config/serverurl', serverURL);
|
||||
const res2 = await sendAdminRequest(
|
||||
'config/federation/username',
|
||||
fediUsername
|
||||
|
@ -73,28 +70,47 @@ test('set required parameters and enable federation', async (done) => {
|
|||
|
||||
test('verify responses of /.well-known/webfinger when federation is enabled', async (done) => {
|
||||
const resNoResource = request.get('/.well-known/webfinger').expect(400);
|
||||
const resBadResource = request.get(
|
||||
'/.well-known/webfinger?resource=' + fediUsername + '@' + serverName
|
||||
).expect(400);
|
||||
const resBadResource2 = request.get(
|
||||
'/.well-known/webfinger?resource=notacct:' + fediUsername + '@' + serverName
|
||||
).expect(400);
|
||||
const resBadServer = request.get(
|
||||
'/.well-known/webfinger?resource=acct:' + fediUsername + '@not' + serverName
|
||||
).expect(404);
|
||||
const resBadUser = request.get(
|
||||
'/.well-known/webfinger?resource=acct:not' + fediUsername + '@' + serverName
|
||||
).expect(404);
|
||||
const resNoAccept = request.get(
|
||||
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
|
||||
).expect(200)
|
||||
const resBadResource = request
|
||||
.get('/.well-known/webfinger?resource=' + fediUsername + '@' + serverName)
|
||||
.expect(400);
|
||||
const resBadResource2 = request
|
||||
.get(
|
||||
'/.well-known/webfinger?resource=notacct:' +
|
||||
fediUsername +
|
||||
'@' +
|
||||
serverName
|
||||
)
|
||||
.expect(400);
|
||||
const resBadServer = request
|
||||
.get(
|
||||
'/.well-known/webfinger?resource=acct:' +
|
||||
fediUsername +
|
||||
'@not' +
|
||||
serverName
|
||||
)
|
||||
.expect(404);
|
||||
const resBadUser = request
|
||||
.get(
|
||||
'/.well-known/webfinger?resource=acct:not' +
|
||||
fediUsername +
|
||||
'@' +
|
||||
serverName
|
||||
)
|
||||
.expect(404);
|
||||
const resNoAccept = request
|
||||
.get(
|
||||
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
|
||||
)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((res) => {
|
||||
parseJson(res.text);
|
||||
});
|
||||
const resWithAccept = request.get(
|
||||
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
|
||||
).expect(200)
|
||||
const resWithAccept = request
|
||||
.get(
|
||||
'/.well-known/webfinger?resource=acct:' + fediUsername + '@' + serverName
|
||||
)
|
||||
.expect(200)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
.then((res) => {
|
||||
|
@ -104,14 +120,16 @@ test('verify responses of /.well-known/webfinger when federation is enabled', as
|
|||
});
|
||||
|
||||
test('verify responses of /.well-known/host-meta when federation is enabled', async (done) => {
|
||||
const res = request.get('/.well-known/host-meta')
|
||||
const res = request
|
||||
.get('/.well-known/host-meta')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /xml/);
|
||||
done();
|
||||
});
|
||||
|
||||
test('verify responses of /.well-known/nodeinfo when federation is enabled', async (done) => {
|
||||
const res = request.get('/.well-known/nodeinfo')
|
||||
const res = request
|
||||
.get('/.well-known/nodeinfo')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((res) => {
|
||||
|
@ -121,7 +139,8 @@ test('verify responses of /.well-known/nodeinfo when federation is enabled', asy
|
|||
});
|
||||
|
||||
test('verify responses of /.well-known/x-nodeinfo2 when federation is enabled', async (done) => {
|
||||
const res = request.get('/.well-known/x-nodeinfo2')
|
||||
const res = request
|
||||
.get('/.well-known/x-nodeinfo2')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((res) => {
|
||||
|
@ -143,7 +162,8 @@ test('verify responses of /nodeinfo/2.0 when federation is enabled', async (done
|
|||
});
|
||||
|
||||
test('verify responses of /api/v1/instance when federation is enabled', async (done) => {
|
||||
const res = request.get('/api/v1/instance')
|
||||
const res = request
|
||||
.get('/api/v1/instance')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.then((res) => {
|
||||
|
@ -153,15 +173,17 @@ test('verify responses of /api/v1/instance when federation is enabled', async (d
|
|||
});
|
||||
|
||||
test('verify responses of /federation/user/ when federation is enabled', async (done) => {
|
||||
const resNoAccept = request.get('/federation/user/')
|
||||
.expect(307);
|
||||
const resWithAccept = request.get('/federation/user/')
|
||||
const resNoAccept = request.get('/federation/user/').expect(307);
|
||||
const resWithAccept = request
|
||||
.get('/federation/user/')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404);
|
||||
const resWithAcceptWrongUsername = request.get('/federation/user/not' + fediUsername)
|
||||
const resWithAcceptWrongUsername = request
|
||||
.get('/federation/user/not' + fediUsername)
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404);
|
||||
const resWithAcceptUsername = request.get('/federation/user/' + fediUsername)
|
||||
const resWithAcceptUsername = request
|
||||
.get('/federation/user/' + fediUsername)
|
||||
.set('Accept', 'application/json')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
|
@ -172,9 +194,9 @@ test('verify responses of /federation/user/ when federation is enabled', async (
|
|||
});
|
||||
|
||||
test('verify responses of /federation/ when federation is enabled', async (done) => {
|
||||
const resNoAccept = request.get('/federation/')
|
||||
.expect(307);
|
||||
const resWithAccept = request.get('/federation/')
|
||||
const resNoAccept = request.get('/federation/').expect(307);
|
||||
const resWithAccept = request
|
||||
.get('/federation/')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404);
|
||||
done();
|
||||
|
|
|
@ -3,91 +3,91 @@
|
|||
set -e
|
||||
|
||||
function install_ffmpeg() {
|
||||
# install a specific version of ffmpeg
|
||||
# install a specific version of ffmpeg
|
||||
|
||||
FFMPEG_VER="4.4.1"
|
||||
FFMPEG_PATH="$(pwd)/ffmpeg-$FFMPEG_VER"
|
||||
PATH=$FFMPEG_PATH:$PATH
|
||||
FFMPEG_VER="4.4.1"
|
||||
FFMPEG_PATH="$(pwd)/ffmpeg-$FFMPEG_VER"
|
||||
PATH=$FFMPEG_PATH:$PATH
|
||||
|
||||
if ! [[ -d "$FFMPEG_PATH" ]]; then
|
||||
mkdir "$FFMPEG_PATH"
|
||||
fi
|
||||
if ! [[ -d "$FFMPEG_PATH" ]]; then
|
||||
mkdir "$FFMPEG_PATH"
|
||||
fi
|
||||
|
||||
pushd "$FFMPEG_PATH" >/dev/null
|
||||
pushd "$FFMPEG_PATH" >/dev/null
|
||||
|
||||
if [[ -x "$FFMPEG_PATH/ffmpeg" ]]; then
|
||||
|
||||
ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
|
||||
if [[ -x "$FFMPEG_PATH/ffmpeg" ]]; then
|
||||
|
||||
if [[ "$ffmpeg_version" == "$FFMPEG_VER-static" ]]; then
|
||||
popd >/dev/null
|
||||
return 0
|
||||
else
|
||||
mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg"
|
||||
fi
|
||||
fi
|
||||
ffmpeg_version=$("$FFMPEG_PATH/ffmpeg" -version | awk -F 'ffmpeg version' '{print $2}' | awk 'NR==1{print $1}')
|
||||
|
||||
rm -f ffmpeg.zip
|
||||
curl -sL --fail https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v${FFMPEG_VER}/ffmpeg-${FFMPEG_VER}-linux-64.zip --output ffmpeg.zip >/dev/null
|
||||
unzip -o ffmpeg.zip >/dev/null && rm -f ffmpeg.zip
|
||||
chmod +x ffmpeg
|
||||
PATH=$FFMPEG_PATH:$PATH
|
||||
if [[ "$ffmpeg_version" == "$FFMPEG_VER-static" ]]; then
|
||||
popd >/dev/null
|
||||
return 0
|
||||
else
|
||||
mv "$FFMPEG_PATH/ffmpeg" "$FFMPEG_PATH/ffmpeg.bk" || rm -f "$FFMPEG_PATH/ffmpeg"
|
||||
fi
|
||||
fi
|
||||
|
||||
popd >/dev/null
|
||||
rm -f ffmpeg.zip
|
||||
curl -sL --fail https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v${FFMPEG_VER}/ffmpeg-${FFMPEG_VER}-linux-64.zip --output ffmpeg.zip >/dev/null
|
||||
unzip -o ffmpeg.zip >/dev/null && rm -f ffmpeg.zip
|
||||
chmod +x ffmpeg
|
||||
PATH=$FFMPEG_PATH:$PATH
|
||||
|
||||
popd >/dev/null
|
||||
}
|
||||
|
||||
function start_owncast() {
|
||||
# Build and run owncast from source
|
||||
echo "Building owncast..."
|
||||
pushd "$(git rev-parse --show-toplevel)" >/dev/null
|
||||
go build -o owncast main.go
|
||||
# Build and run owncast from source
|
||||
echo "Building owncast..."
|
||||
pushd "$(git rev-parse --show-toplevel)" >/dev/null
|
||||
go build -o owncast main.go
|
||||
|
||||
echo "Running owncast..."
|
||||
./owncast -database "$TEMP_DB" &
|
||||
SERVER_PID=$!
|
||||
popd >/dev/null
|
||||
echo "Running owncast..."
|
||||
./owncast -disableResponseCaching -database "$TEMP_DB" &
|
||||
SERVER_PID=$!
|
||||
popd >/dev/null
|
||||
|
||||
sleep 5
|
||||
sleep 5
|
||||
|
||||
}
|
||||
|
||||
function start_stream() {
|
||||
# Start streaming the test file over RTMP to the local owncast instance.
|
||||
../../ocTestStream.sh &
|
||||
STREAM_PID=$!
|
||||
# Start streaming the test file over RTMP to the local owncast instance.
|
||||
../../ocTestStream.sh &
|
||||
STREAM_PID=$!
|
||||
|
||||
echo "Waiting for stream to start..."
|
||||
sleep 12
|
||||
echo "Waiting for stream to start..."
|
||||
sleep 12
|
||||
}
|
||||
|
||||
function update_storage_config() {
|
||||
echo "Configuring external storage to use ${S3_BUCKET}..."
|
||||
echo "Configuring external storage to use ${S3_BUCKET}..."
|
||||
|
||||
# Hard-coded to admin:abc123 for auth
|
||||
curl --fail 'http://localhost:8080/api/admin/config/s3' \
|
||||
-H 'Authorization: Basic YWRtaW46YWJjMTIz' \
|
||||
--data-raw "{\"value\":{\"accessKey\":\"${S3_ACCESS_KEY}\",\"acl\":\"\",\"bucket\":\"${S3_BUCKET}\",\"enabled\":true,\"endpoint\":\"${S3_ENDPOINT}\",\"region\":\"${S3_REGION}\",\"secret\":\"${S3_SECRET}\",\"servingEndpoint\":\"\"}}"
|
||||
# Hard-coded to admin:abc123 for auth
|
||||
curl --fail 'http://localhost:8080/api/admin/config/s3' \
|
||||
-H 'Authorization: Basic YWRtaW46YWJjMTIz' \
|
||||
--data-raw "{\"value\":{\"accessKey\":\"${S3_ACCESS_KEY}\",\"acl\":\"\",\"bucket\":\"${S3_BUCKET}\",\"enabled\":true,\"endpoint\":\"${S3_ENDPOINT}\",\"region\":\"${S3_REGION}\",\"secret\":\"${S3_SECRET}\",\"servingEndpoint\":\"\"}}"
|
||||
}
|
||||
|
||||
function kill_with_kids() {
|
||||
# kill a process and all its children (by pid)! return no error.
|
||||
# kill a process and all its children (by pid)! return no error.
|
||||
|
||||
if [[ -n $1 ]]; then
|
||||
mapfile -t CHILDREN_PID_LIST < <(ps --ppid "$1" -o pid= &>/dev/null || true)
|
||||
for child_pid in "${CHILDREN_PID_LIST[@]}"; do
|
||||
kill "$child_pid" &>/dev/null || true
|
||||
wait "$child_pid" &>/dev/null || true
|
||||
done
|
||||
kill "$1" &>/dev/null || true
|
||||
wait "$1" &>/dev/null || true
|
||||
fi
|
||||
if [[ -n $1 ]]; then
|
||||
mapfile -t CHILDREN_PID_LIST < <(ps --ppid "$1" -o pid= &>/dev/null || true)
|
||||
for child_pid in "${CHILDREN_PID_LIST[@]}"; do
|
||||
kill "$child_pid" &>/dev/null || true
|
||||
wait "$child_pid" &>/dev/null || true
|
||||
done
|
||||
kill "$1" &>/dev/null || true
|
||||
wait "$1" &>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
function finish() {
|
||||
echo "Cleaning up..."
|
||||
kill_with_kids "$STREAM_PID"
|
||||
kill "$SERVER_PID" &>/dev/null || true
|
||||
wait "$SERVER_PID" &>/dev/null || true
|
||||
echo "Cleaning up..."
|
||||
kill_with_kids "$STREAM_PID"
|
||||
kill "$SERVER_PID" &>/dev/null || true
|
||||
wait "$SERVER_PID" &>/dev/null || true
|
||||
rm -fr "$TEMP_DB"
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue