diff --git a/docs/docs/administration/configuration-files.md b/docs/docs/administration/configuration-files.md
index d58741d8e89a..97186e593f6b 100644
--- a/docs/docs/administration/configuration-files.md
+++ b/docs/docs/administration/configuration-files.md
@@ -32,8 +32,9 @@ Starting with BigBlueButton 2.3 many of the configuration files have local overr
| /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml | /etc/bigbluebutton/bbb-webrtc-sfu/production.yml | Arrays are merged by replacement |
| /usr/local/bigbluebutton/bbb-pads/config/settings.json | /etc/bigbluebutton/bbb-pads.json | Arrays are merged by replacement |
| /usr/share/bbb-shared-notes-server/config/default.yml | /etc/bigbluebutton/bbb-shared-notes-server.yml | |
-| /usr/local/bigbluebutton/core/scripts/bigbluebutton.yml | /etc/bigbluebutton/recording/recording.yml |
+| /usr/local/bigbluebutton/core/scripts/bigbluebutton.yml | /etc/bigbluebutton/recording/recording.yml | Honored by all recording formats; re-read on every processing step, so changes do not require a restart |
| /usr/local/bigbluebutton/core/scripts/presentation.yml | /etc/bigbluebutton/recording/presentation.yml |
+| /usr/local/bigbluebutton/core/scripts/video.yml | /etc/bigbluebutton/recording/video.yml | Individual `presets` are merged by key; other settings are replaced |
| /etc/cron.daily/bigbluebutton | /etc/default/bigbluebutton-cron-config | Only variables allowed in the override
@@ -66,6 +67,15 @@ services {
}
```
+For the recording pipeline, `/etc/bigbluebutton/recording/recording.yml` overrides settings from `/usr/local/bigbluebutton/core/scripts/bigbluebutton.yml` for every recording format (presentation, video, screenshare, notes, podcast). For example, the following `recording.yml` makes published recording links use a different hostname and protocol:
+
+```yml
+playback_host: playback.example.com
+playback_protocol: https
+```
+
+The override is re-read on every recording processing step, so a change takes effect for the next recording without restarting the recording workers. The playback link in a recording's `metadata.xml` is regenerated at publish time, so links of an already-published recording can be corrected by rebuilding it: `bbb-record --rebuild `.
+
## HTML5 Client
### Configuration files
diff --git a/record-and-playback/core/lib/recordandplayback.rb b/record-and-playback/core/lib/recordandplayback.rb
index 04410ee8cd58..24257a20b696 100644
--- a/record-and-playback/core/lib/recordandplayback.rb
+++ b/record-and-playback/core/lib/recordandplayback.rb
@@ -237,19 +237,16 @@ def self.rap_scripts_path
File.join(BigBlueButton.rap_core_path, 'scripts')
end
- def self.read_props
- return @props if @props
-
- filepathRecOverride = "/etc/bigbluebutton/recording/recording.yml"
- hasOverride = File.file?(filepathRecOverride)
-
+ # Do not cache the result: long-lived processes (e.g. the resque workers)
+ # must pick up changes to the override file without a restart.
+ def self.read_props(override_path: '/etc/bigbluebutton/recording/recording.yml')
filepath = File.join(BigBlueButton.rap_scripts_path, 'bigbluebutton.yml')
- @props = YAML::load(File.open(filepath))
- if (hasOverride)
- recOverrideProps = YAML::load(File.open(filepathRecOverride))
- @props = @props.merge(recOverrideProps)
+ props = YAML.safe_load(File.read(filepath), aliases: true) || {}
+ if File.file?(override_path)
+ override_props = YAML.safe_load(File.read(override_path), aliases: true) || {}
+ props = props.merge(override_props)
end
- @props
+ props
end
def self.create_redis_publisher
diff --git a/record-and-playback/core/test/recordandplayback/test_read_props.rb b/record-and-playback/core/test/recordandplayback/test_read_props.rb
new file mode 100644
index 000000000000..d5174823d7bb
--- /dev/null
+++ b/record-and-playback/core/test/recordandplayback/test_read_props.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'minitest/autorun'
+require 'tmpdir'
+
+require 'recordandplayback'
+
+class TestReadProps < Minitest::Test
+ def test_returns_base_props_when_override_missing
+ props = BigBlueButton.read_props(override_path: '/nonexistent/recording.yml')
+
+ assert_kind_of(Hash, props)
+ refute_nil(props['playback_host'])
+ end
+
+ def test_override_merges_over_base_props
+ Dir.mktmpdir do |dir|
+ override = File.join(dir, 'recording.yml')
+ File.write(override, "playback_host: override.example.com\n")
+
+ props = BigBlueButton.read_props(override_path: override)
+
+ assert_equal('override.example.com', props['playback_host'])
+ refute_nil(props['recording_dir'])
+ end
+ end
+
+ def test_rereads_override_on_each_call
+ Dir.mktmpdir do |dir|
+ override = File.join(dir, 'recording.yml')
+
+ File.write(override, "playback_host: first.example.com\n")
+ assert_equal('first.example.com', BigBlueButton.read_props(override_path: override)['playback_host'])
+
+ File.write(override, "playback_host: second.example.com\n")
+ assert_equal('second.example.com', BigBlueButton.read_props(override_path: override)['playback_host'])
+ end
+ end
+
+ def test_empty_override_file_is_ignored
+ Dir.mktmpdir do |dir|
+ override = File.join(dir, 'recording.yml')
+ File.write(override, '')
+
+ props = BigBlueButton.read_props(override_path: override)
+
+ assert_kind_of(Hash, props)
+ refute_nil(props['playback_host'])
+ end
+ end
+end
diff --git a/record-and-playback/notes/scripts/process/notes.rb b/record-and-playback/notes/scripts/process/notes.rb
index f9ba7690c4dd..1114bcc9429f 100644
--- a/record-and-playback/notes/scripts/process/notes.rb
+++ b/record-and-playback/notes/scripts/process/notes.rb
@@ -38,7 +38,7 @@
meeting_id = opts[:meeting_id]
# This script lives in scripts/archive/steps while properties.yaml lives in scripts/
-props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
+props = BigBlueButton.read_props
notes_props = YAML::load(File.open('notes.yml'))
format = notes_props['format']
diff --git a/record-and-playback/notes/scripts/publish/notes.rb b/record-and-playback/notes/scripts/publish/notes.rb
index 12963d9d6529..0a5176d8d78a 100644
--- a/record-and-playback/notes/scripts/publish/notes.rb
+++ b/record-and-playback/notes/scripts/publish/notes.rb
@@ -30,7 +30,7 @@
# This script lives in scripts/archive/steps while properties.yaml lives in scripts/
-bbb_props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
+bbb_props = BigBlueButton.read_props
notes_props = YAML::load(File.open('notes.yml'))
opts = Optimist::options do
diff --git a/record-and-playback/podcast/scripts/process/podcast.rb b/record-and-playback/podcast/scripts/process/podcast.rb
index de23d648d4ec..ed02a131e975 100755
--- a/record-and-playback/podcast/scripts/process/podcast.rb
+++ b/record-and-playback/podcast/scripts/process/podcast.rb
@@ -38,7 +38,7 @@
meeting_id = opts[:meeting_id]
# This script lives in scripts/archive/steps while properties.yaml lives in scripts/
-props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
+props = BigBlueButton.read_props
podcast_props = YAML::load(File.open('podcast.yml'))
recording_dir = props['recording_dir']
diff --git a/record-and-playback/podcast/scripts/publish/podcast.rb b/record-and-playback/podcast/scripts/publish/podcast.rb
index bc07e401f491..fc877017a704 100755
--- a/record-and-playback/podcast/scripts/publish/podcast.rb
+++ b/record-and-playback/podcast/scripts/publish/podcast.rb
@@ -30,7 +30,7 @@
# This script lives in scripts/archive/steps while properties.yaml lives in scripts/
-bbb_props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
+bbb_props = BigBlueButton.read_props
podcast_props = YAML::load(File.open('podcast.yml'))
opts = Optimist::options do
diff --git a/record-and-playback/screenshare/scripts/process/screenshare.rb b/record-and-playback/screenshare/scripts/process/screenshare.rb
index 9b023a7f8247..b97f4f87ac87 100644
--- a/record-and-playback/screenshare/scripts/process/screenshare.rb
+++ b/record-and-playback/screenshare/scripts/process/screenshare.rb
@@ -40,7 +40,7 @@
end
# Load parameters and set up paths
-props = YAML::load(File.open(File.expand_path('../../bigbluebutton.yml', __FILE__)))
+props = BigBlueButton.read_props
screenshare_props = YAML::load(File.open(File.expand_path('../../screenshare.yml', __FILE__)))
recording_dir = props['recording_dir']
diff --git a/record-and-playback/screenshare/scripts/publish/screenshare.rb b/record-and-playback/screenshare/scripts/publish/screenshare.rb
index 18734effd771..7252c47102e3 100644
--- a/record-and-playback/screenshare/scripts/publish/screenshare.rb
+++ b/record-and-playback/screenshare/scripts/publish/screenshare.rb
@@ -37,7 +37,7 @@
end
# Load parameters and set up paths
-props = YAML::load(File.open(File.expand_path('../../bigbluebutton.yml', __FILE__)))
+props = BigBlueButton.read_props
screenshare_props = YAML::load(File.open(File.expand_path('../../screenshare.yml', __FILE__)))
process_dir = "#{props['recording_dir']}/process/screenshare/#{meeting_id}"
@@ -81,8 +81,16 @@
"#{publish_dir}/caption_#{caption['locale']}.vtt")
end
-# Copy over metadata xml file
-FileUtils.cp("#{process_dir}/metadata.xml", "#{publish_dir}/metadata.xml")
+# Refresh the playback link from current props (honoring recording.yml
+# overrides) and write the updated metadata to the publish directory, rather
+# than copying the process-time metadata.xml with a possibly-stale link.
+metadata_xml = Nokogiri::XML(File.open("#{process_dir}/metadata.xml"))
+link = metadata_xml.at_xpath('/recording/playback/link')
+if link
+ link.content = "#{props['playback_protocol']}://#{props['playback_host']}/recording/screenshare/#{meeting_id}/"
+ logger.info "Refreshed playback link to #{link.content}"
+end
+File.write("#{publish_dir}/metadata.xml", metadata_xml.to_xml)
# Copy over css and js support files
FileUtils.cp_r("#{process_dir}/css", publish_dir)
diff --git a/record-and-playback/video/scripts/publish/video.rb b/record-and-playback/video/scripts/publish/video.rb
index e0fd98755f4a..0888ecc5ca7c 100755
--- a/record-and-playback/video/scripts/publish/video.rb
+++ b/record-and-playback/video/scripts/publish/video.rb
@@ -128,8 +128,15 @@
"#{publish_dir}/caption_#{caption['locale']}.vtt")
end
-# Copy over metadata xml file
-FileUtils.cp("#{process_dir}/metadata.xml", "#{publish_dir}/metadata.xml")
+# Refresh the playback link from current props (honoring recording.yml
+# overrides) and write the updated metadata to the publish directory, rather
+# than copying the process-time metadata.xml with a possibly-stale link.
+link = metadata_xml.at_xpath('/recording/playback/link')
+if link
+ link.content = "#{props['playback_protocol']}://#{props['playback_host']}/playback/video/#{meeting_id}/"
+ logger.info("Refreshed playback link to #{link.content}")
+end
+File.write("#{publish_dir}/metadata.xml", metadata_xml.to_xml)
# Get raw size of presentation files
raw_dir = "#{recording_dir}/raw/#{meeting_id}"