From f68b0e4a169494f5c2b0b686d1855e3cceac5dc0 Mon Sep 17 00:00:00 2001 From: Felipe Cecagno <fcecagno@gmail.com> Date: Mon, 1 Sep 2014 17:53:57 -0300 Subject: [PATCH] cleanup --- bigbluebutton-config/bin/bbb-record | 4 +- record-and-playback/core/Gemfile | 9 +-- .../generators/mconf_processor.rb | 6 +- record-and-playback/deploy.sh | 16 ++---- .../mconf/scripts/mconf-god-conf.rb | 55 ------------------- record-and-playback/mconf/scripts/mconf.yml | 2 - .../scripts/mconf-decrypter.rb} | 35 ++++++++---- .../scripts/mconf-decrypter.yml | 2 + .../scripts/mconf-recording-decrypter.initd | 44 +++++++++++++++ .../scripts/mconf-recording-decrypter.monit | 3 + .../scripts/mconf_encrypted.nginx} | 2 +- .../scripts/process/mconf_encrypted.rb} | 6 +- .../scripts/publish/mconf_encrypted.rb} | 25 ++++----- 13 files changed, 102 insertions(+), 107 deletions(-) delete mode 100755 record-and-playback/mconf/scripts/mconf-god-conf.rb delete mode 100644 record-and-playback/mconf/scripts/mconf.yml rename record-and-playback/{mconf/scripts/mconf-decrypt.rb => mconf_decrypter/scripts/mconf-decrypter.rb} (82%) create mode 100644 record-and-playback/mconf_decrypter/scripts/mconf-decrypter.yml create mode 100644 record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.initd create mode 100644 record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.monit rename record-and-playback/{mconf/scripts/mconf.nginx => mconf_encrypted/scripts/mconf_encrypted.nginx} (96%) rename record-and-playback/{mconf/scripts/process/mconf.rb => mconf_encrypted/scripts/process/mconf_encrypted.rb} (91%) mode change 100644 => 100755 rename record-and-playback/{mconf/scripts/publish/mconf.rb => mconf_encrypted/scripts/publish/mconf_encrypted.rb} (89%) diff --git a/bigbluebutton-config/bin/bbb-record b/bigbluebutton-config/bin/bbb-record index b3ccb5b2f5..38e5bb6e6f 100755 --- a/bigbluebutton-config/bin/bbb-record +++ b/bigbluebutton-config/bin/bbb-record @@ -329,12 +329,12 @@ if [ $REPUBLISH ]; then fi if [ $CLEAN ]; then - sudo /etc/init.d/bbb-record stop + sudo /etc/init.d/bbb-record-core stop for type in $TYPES; do echo " clearning logs in /var/log/bigbluebutton/$type" find /var/log/bigbluebutton/$type -name "*.log" -exec sudo rm '{}' \; done - sudo /etc/init.d/bbb-record start + sudo /etc/init.d/bbb-record-core start fi if [ $DELETE ]; then diff --git a/record-and-playback/core/Gemfile b/record-and-playback/core/Gemfile index 904f315108..8a9b99f378 100644 --- a/record-and-playback/core/Gemfile +++ b/record-and-playback/core/Gemfile @@ -19,12 +19,9 @@ source "http://rubygems.org" -gem "rspec", "2.0.0", :require => "spec" -gem "cucumber", "0.9.2" -gem "redis", "2.1.1" -#gem "SystemTimer", "1.2.3" -gem "nokogiri", "1.4.4" -gem "resque", "1.15.0" +gem "redis" +gem "nokogiri" +gem "resque" gem "mime-types" gem "streamio-ffmpeg" gem "rubyzip" diff --git a/record-and-playback/core/lib/recordandplayback/generators/mconf_processor.rb b/record-and-playback/core/lib/recordandplayback/generators/mconf_processor.rb index 441d5cd99a..cff98408f4 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/mconf_processor.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/mconf_processor.rb @@ -25,7 +25,7 @@ require 'fileutils' require 'builder' require 'mime/types' require 'digest/md5' -require 'zip/zip' +require 'zip' module BigBlueButton @@ -33,7 +33,7 @@ module BigBlueButton def self.zip_directory (directory, zipped_file) BigBlueButton.logger.info("Task: Zipping directory... #{zipped_file} #{directory}") #files = [webcam, deskshare, dublincore, manifest] - Zip::ZipFile.open(zipped_file, Zip::ZipFile::CREATE) do |zipfile| + Zip::File.open(zipped_file, Zip::File::CREATE) do |zipfile| Dir["#{directory}/**/**"].reject{|f|f==zipped_file}.each do |file| zipfile.add(file.sub(directory+'/', ''), file) end @@ -41,7 +41,7 @@ module BigBlueButton end def self.unzip(unzip_dir, zipfile) - Zip::ZipFile.open(zipfile) do |zip_file| + Zip::File.open(zipfile) do |zip_file| zip_file.each do |f| f_path=File.join(unzip_dir, f.name) FileUtils.mkdir_p(File.dirname(f_path)) diff --git a/record-and-playback/deploy.sh b/record-and-playback/deploy.sh index 4a86885967..1eec7dddb1 100755 --- a/record-and-playback/deploy.sh +++ b/record-and-playback/deploy.sh @@ -25,11 +25,6 @@ sudo rm -rf /usr/local/bigbluebutton/core/lib sudo cp -r core/lib /usr/local/bigbluebutton/core/ sudo rm -rf /usr/local/bigbluebutton/core/scripts sudo cp -r core/scripts /usr/local/bigbluebutton/core/ -sudo rm -rf /etc/bigbluebutton/god -sudo cp -r core/god/god /etc/bigbluebutton/ -sudo rm -f /etc/init.d/bbb-record-core -sudo cp core/god/initd.god /etc/init.d/bbb-record-core -sudo chmod 0755 /etc/init.d/bbb-record-core sudo rm -rf /var/bigbluebutton/playback/* function deploy_format() { @@ -40,17 +35,18 @@ function deploy_format() { scripts_dir="$format/scripts" if [ -d $playback_dir ]; then sudo cp -r $playback_dir /var/bigbluebutton/playback/; fi if [ -d $scripts_dir ]; then sudo cp -r $scripts_dir/* /usr/local/bigbluebutton/core/scripts/; fi - sudo mkdir -p /var/log/bigbluebutton/$format + sudo mkdir -p /var/log/bigbluebutton/$format /var/bigbluebutton/published/$format /var/bigbluebutton/recording/publish/$format done } RECORDING_SERVER=false if $RECORDING_SERVER ; then - sudo cp mconf/scripts/mconf-god-conf.rb /etc/bigbluebutton/god/conf/ - sudo cp mconf/scripts/mconf-decrypt.rb /usr/local/bigbluebutton/core/scripts/ deploy_format "presentation" + deploy_format "mconf_decrypter" + sudo mv /usr/local/bigbluebutton/core/scripts/mconf-recording-decrypter.initd /etc/init.d/mconf-recording-decrypter + sudo mv /usr/local/bigbluebutton/core/scripts/mconf-recording-decrypter.monit /etc/monit/conf.d/mconf-recording-decrypter else - deploy_format "mconf" + deploy_format "mconf_encrypted" fi sudo mkdir -p /var/bigbluebutton/playback/ @@ -63,7 +59,7 @@ sudo mkdir -p /var/bigbluebutton/recording/status/processed/ sudo mkdir -p /var/bigbluebutton/recording/status/sanity/ sudo mv /usr/local/bigbluebutton/core/scripts/*.nginx /etc/bigbluebutton/nginx/ -sudo chown -R tomcat6:tomcat6 /var/bigbluebutton/ /var/log/bigbluebutton/ +sudo chown -R tomcat7:tomcat7 /var/bigbluebutton/ /var/log/bigbluebutton/ sudo chown -R red5:red5 /var/bigbluebutton/deskshare/ sudo chown -R freeswitch:daemon /var/bigbluebutton/meetings/ diff --git a/record-and-playback/mconf/scripts/mconf-god-conf.rb b/record-and-playback/mconf/scripts/mconf-god-conf.rb deleted file mode 100755 index 31bbcc6dc1..0000000000 --- a/record-and-playback/mconf/scripts/mconf-god-conf.rb +++ /dev/null @@ -1,55 +0,0 @@ -# -# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -# -# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). -# -# This program is free software; you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free Software -# Foundation; either version 3.0 of the License, or (at your option) any later -# version. -# -# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. -# - -# NOTE: -# Copy into /etc/bigbluebutton/god/conf -# sudo cp matt-pub-god-conf.rb /etc/bigbluebutton/god/conf/matterhorn-publish-conf.rb -# -# Monitors the BigBlueButton Matterhorn publisher process -God.watch do |w| - # The name of the watcher - w.name = "mconf-decrypt" - - # The default time for reporting the state of the monitored process - w.interval = 1.minute - - # Start the process - w.start = "ruby mconf-decrypt.rb" - - # Start your process in this directory - w.dir = "/usr/local/bigbluebutton/core/scripts/" - - # Time to wait before monitoring, after starting the process - w.start_grace = 30.seconds - - # Cleans the pid file before starting the process. - # god will daemonizes the process - w.behavior(:clean_pid_file) - - - # Start the process if it is not running - # And report its status every 30 seconds - # In other words god revives the process every time it dies - w.start_if do |start| - start.condition(:process_running) do |c| - c.interval = 30.seconds - c.running = false - end - end -end - diff --git a/record-and-playback/mconf/scripts/mconf.yml b/record-and-playback/mconf/scripts/mconf.yml deleted file mode 100644 index 0530d96d25..0000000000 --- a/record-and-playback/mconf/scripts/mconf.yml +++ /dev/null @@ -1,2 +0,0 @@ -get_recordings_url: http://143.54.10.52/bigbluebutton/api/getRecordings?checksum=39ebeadb772553a206d43857f6287b4536b1c32c -private_key: /home/mconf/dev/bigbluebutton/record-and-playback/key.pem diff --git a/record-and-playback/mconf/scripts/mconf-decrypt.rb b/record-and-playback/mconf_decrypter/scripts/mconf-decrypter.rb similarity index 82% rename from record-and-playback/mconf/scripts/mconf-decrypt.rb rename to record-and-playback/mconf_decrypter/scripts/mconf-decrypter.rb index 8cc5461613..7098ab18c4 100755 --- a/record-and-playback/mconf/scripts/mconf-decrypt.rb +++ b/record-and-playback/mconf_decrypter/scripts/mconf-decrypter.rb @@ -1,3 +1,4 @@ +#!/usr/bin/ruby # Set encoding to utf-8 # encoding: UTF-8 # @@ -26,11 +27,11 @@ require 'rexml/document' require 'open-uri' require 'digest/md5' -BigBlueButton.logger = Logger.new("/var/log/bigbluebutton/decrypt.log",'daily' ) +BigBlueButton.logger = Logger.new("/var/log/bigbluebutton/mconf_decrypter.log",'daily' ) #BigBlueButton.logger = Logger.new(STDOUT) bbb_props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml')) -mconf_props = YAML::load(File.open('mconf.yml')) +mconf_props = YAML::load(File.open('mconf-decrypter.yml')) # these properties must be global variables (starting with $) $private_key = mconf_props['private_key'] @@ -46,10 +47,11 @@ def fetchRecordings(url) doc = Nokogiri::XML(Net::HTTP.get_response(URI.parse(url)).body) returncode = doc.xpath("//returncode") if returncode.empty? or returncode.text != "SUCCESS" - raise "getRecordings didn't return success:\n#{doc.to_xml(:indent => 2)}" + BigBlueButton.logger.error "getRecordings didn't return success:\n#{doc.to_xml(:indent => 2)}" + return false end rescue - BigBlueButton.logger.debug("Exception occurred: #{$!}") + BigBlueButton.logger.error("Exception occurred: #{$!}") return false end @@ -78,9 +80,15 @@ def fetchRecordings(url) BigBlueButton.logger.debug("md5_value = #{md5_value}") BigBlueButton.logger.info("Downloading the encrypted file to #{encrypted_file}") - writeOut = open(encrypted_file, "wb") - writeOut.write(open(file_url).read) - writeOut.close + + begin + writeOut = open(encrypted_file, "wb") + writeOut.write(open(file_url).read) + writeOut.close + rescue Exception => e + BigBlueButton.logger.error "Failed to download the encrypted file: #{e.to_s}" + next + end md5_calculated = Digest::MD5.file(encrypted_file) @@ -97,13 +105,15 @@ def fetchRecordings(url) if key_file != decrypted_key_file BigBlueButton.logger.debug("Locating private key") if not File.exists?("#{$private_key}") - raise "Couldn't find the private key on #{$private_key}" + BigBlueButton.logger.error "Couldn't find the private key on #{$private_key}" + next end BigBlueButton.logger.debug("Decrypting recording key") command = "openssl rsautl -decrypt -inkey #{$private_key} < #{key_file} > #{decrypted_key_file}" - status = BigBlueButton.execute(command) + status = BigBlueButton.execute(command, false) if not status.success? - raise "Couldn't decrypt the random key with the server private key" + BigBlueButton.logger.error "Couldn't decrypt the random key with the server private key" + next end FileUtils.rm_r "#{key_file}" else @@ -112,9 +122,10 @@ def fetchRecordings(url) BigBlueButton.logger.debug("Decrypting the recording file") command = "openssl enc -aes-256-cbc -d -pass file:#{decrypted_key_file} < #{encrypted_file} > #{decrypted_file}" - status = BigBlueButton.execute(command) + status = BigBlueButton.execute(command, false) if not status.success? - raise "Couldn't decrypt the recording file using the random key" + BigBlueButton.logger.error "Couldn't decrypt the recording file using the random key" + next end BigBlueButton::MconfProcessor.unzip("#{$raw_dir}/#{meeting_id}", decrypted_file) diff --git a/record-and-playback/mconf_decrypter/scripts/mconf-decrypter.yml b/record-and-playback/mconf_decrypter/scripts/mconf-decrypter.yml new file mode 100644 index 0000000000..9f286e7fe5 --- /dev/null +++ b/record-and-playback/mconf_decrypter/scripts/mconf-decrypter.yml @@ -0,0 +1,2 @@ +get_recordings_url: http://localhost/bigbluebutton/api/getRecordings?checksum=39ebeadb772553a206d43857f6287b4536b1c32c +private_key: /usr/local/bigbluebutton/core/scripts/private.pem diff --git a/record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.initd b/record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.initd new file mode 100644 index 0000000000..276f08e05d --- /dev/null +++ b/record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.initd @@ -0,0 +1,44 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: mconf-recording-decrypter +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: Starts the foo service +### END INIT INFO + +NAME=mconf-recording-decrypter +PID_FILE=/var/run/mconf-recording-decrypter.pid +DIR=/usr/local/bigbluebutton/core/scripts +EXEC=mconf-decrypter.rb +RUN_AS=tomcat7 + +if [ ! -f $DIR/$EXEC ]; then + echo "$DIR/$EXEC not found." + exit +fi + +case "$1" in + start) + echo "Starting $NAME" + cd $DIR + start-stop-daemon -d $DIR --start --background --pidfile $PID_FILE --chuid $RUN_AS:$RUN_AS --make-pidfile --exec $EXEC --quiet + ;; + stop) + echo "Stopping $NAME" + start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --exec $EXEC + ;; + force-reload|restart) + $0 stop + $0 start + ;; + + *) + echo "Use: /etc/init.d/$NAME {start|stop|restart|force-reload}" + exit 1 + ;; +esac + +exit 0 diff --git a/record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.monit b/record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.monit new file mode 100644 index 0000000000..d977722e41 --- /dev/null +++ b/record-and-playback/mconf_decrypter/scripts/mconf-recording-decrypter.monit @@ -0,0 +1,3 @@ +check process mconf-recording-decrypter with pidfile /var/run/mconf-recording-decrypter.pid + start program = "/etc/init.d/mconf-recording-decrypter start" with timeout 60 seconds + stop program = "/etc/init.d/mconf-recording-decrypter stop" diff --git a/record-and-playback/mconf/scripts/mconf.nginx b/record-and-playback/mconf_encrypted/scripts/mconf_encrypted.nginx similarity index 96% rename from record-and-playback/mconf/scripts/mconf.nginx rename to record-and-playback/mconf_encrypted/scripts/mconf_encrypted.nginx index a98b2411c0..bd531918b2 100644 --- a/record-and-playback/mconf/scripts/mconf.nginx +++ b/record-and-playback/mconf_encrypted/scripts/mconf_encrypted.nginx @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License along # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. # - location /mconf/ { + location /mconf_encrypted { root /var/bigbluebutton/published; index index.html index.htm; } diff --git a/record-and-playback/mconf/scripts/process/mconf.rb b/record-and-playback/mconf_encrypted/scripts/process/mconf_encrypted.rb old mode 100644 new mode 100755 similarity index 91% rename from record-and-playback/mconf/scripts/process/mconf.rb rename to record-and-playback/mconf_encrypted/scripts/process/mconf_encrypted.rb index f799226317..05aaa5e228 --- a/record-and-playback/mconf/scripts/process/mconf.rb +++ b/record-and-playback/mconf_encrypted/scripts/process/mconf_encrypted.rb @@ -31,7 +31,7 @@ end meeting_id = opts[:meeting_id] #Mconf process log file -logger = Logger.new("/var/log/bigbluebutton/mconf/process-#{meeting_id}.log", 'daily' ) +logger = Logger.new("/var/log/bigbluebutton/mconf_encrypted/process-#{meeting_id}.log", 'daily' ) BigBlueButton.logger = logger # This script lives in scripts/archive/steps while bigbluebutton.yml lives in scripts/ @@ -42,7 +42,7 @@ raw_presentation_src = props['raw_presentation_src'] meeting_raw_dir = "#{recording_dir}/raw/#{meeting_id}" meeting_raw_presentation_dir = "#{raw_presentation_src}/#{meeting_id}" -meeting_process_dir = "#{recording_dir}/process/mconf/#{meeting_id}" +meeting_process_dir = "#{recording_dir}/process/mconf_encrypted/#{meeting_id}" if not FileTest.directory?(meeting_process_dir) FileUtils.mkdir_p "#{meeting_process_dir}" @@ -55,7 +55,7 @@ if not FileTest.directory?(meeting_process_dir) # BigBlueButton.logger.info("Copying the recording presentation from #{meeting_raw_presentation_dir}/#{meeting_id} to #{meeting_process_dir}/presentation_raw") # FileUtils.cp_r Dir.glob("#{meeting_raw_presentation_dir}/#{meeting_id}/*"), "#{meeting_process_dir}/presentation_raw" - process_done = File.new("#{recording_dir}/status/processed/#{meeting_id}-mconf.done", "w") + process_done = File.new("#{recording_dir}/status/processed/#{meeting_id}-mconf_encrypted.done", "w") process_done.write("Processed #{meeting_id}") process_done.close end diff --git a/record-and-playback/mconf/scripts/publish/mconf.rb b/record-and-playback/mconf_encrypted/scripts/publish/mconf_encrypted.rb similarity index 89% rename from record-and-playback/mconf/scripts/publish/mconf.rb rename to record-and-playback/mconf_encrypted/scripts/publish/mconf_encrypted.rb index d6ca09f5b7..19e5189364 100755 --- a/record-and-playback/mconf/scripts/publish/mconf.rb +++ b/record-and-playback/mconf_encrypted/scripts/publish/mconf_encrypted.rb @@ -25,7 +25,6 @@ require 'cgi' require 'digest/md5' bbb_props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml')) -mconf_props = YAML::load(File.open('mconf.yml')) recording_dir = bbb_props['recording_dir'] playback_host = bbb_props['playback_host'] @@ -36,12 +35,12 @@ done_files = Dir.glob("#{recording_dir}/status/processed/*.done") done_files.each do |df| match = /(.*)-(.*).done/.match df.sub(/.+\//, "") meeting_id = match[1] - if (match[2] == "mconf") - BigBlueButton.logger = Logger.new("/var/log/bigbluebutton/mconf/publish-#{meeting_id}.log", 'daily' ) + if (match[2] == "mconf_encrypted") + BigBlueButton.logger = Logger.new("/var/log/bigbluebutton/mconf_encrypted/publish-#{meeting_id}.log", 'daily' ) - meeting_process_dir = "#{recording_dir}/process/mconf/#{meeting_id}" - meeting_publish_dir = "#{recording_dir}/publish/mconf/#{meeting_id}" - meeting_published_dir = "#{recording_dir}/published/mconf/#{meeting_id}" + meeting_process_dir = "#{recording_dir}/process/mconf_encrypted/#{meeting_id}" + meeting_publish_dir = "#{recording_dir}/publish/mconf_encrypted/#{meeting_id}" + meeting_published_dir = "#{recording_dir}/published/mconf_encrypted/#{meeting_id}" meeting_raw_dir = "#{recording_dir}/raw/#{meeting_id}" meeting_raw_presentation_dir = "#{raw_presentation_src}/#{meeting_id}" @@ -123,9 +122,9 @@ done_files.each do |df| b.end_time(BigBlueButton::Events.last_event_timestamp("#{meeting_process_dir}/events.xml")) b.download { b.format("encrypted") - b.link("http://#{playback_host}/mconf/#{meeting_id}/#{meeting_id}.dat") + b.link("http://#{playback_host}/mconf_encrypted/#{meeting_id}/#{meeting_id}.dat") b.md5(md5sum) - b.key("http://#{playback_host}/mconf/#{meeting_id}/#{key_filename}") + b.key("http://#{playback_host}/mconf_encrypted/#{meeting_id}/#{key_filename}") } b.meta { BigBlueButton::Events.get_meeting_metadata("#{meeting_process_dir}/events.xml").each { |k,v| b.method_missing(k,v) } @@ -136,14 +135,14 @@ done_files.each do |df| metadata_xml.write(metaxml) metadata_xml.close - BigBlueButton.logger.info("Publishing mconf") + BigBlueButton.logger.info("Publishing mconf_encrypted") # Now publish this recording - if not FileTest.directory?("#{published_dir}/mconf") - FileUtils.mkdir_p "#{published_dir}/mconf" + if not FileTest.directory?("#{published_dir}/mconf_encrypted") + FileUtils.mkdir_p "#{published_dir}/mconf_encrypted" end BigBlueButton.logger.info("Publishing files") - FileUtils.cp_r(meeting_publish_dir, "#{published_dir}/mconf") + FileUtils.cp_r(meeting_publish_dir, "#{published_dir}/mconf_encrypted") BigBlueButton.logger.info("Removing processed files: #{meeting_process_dir}") FileUtils.rm_r meeting_process_dir, :force => true @@ -159,7 +158,7 @@ done_files.each do |df| Dir.glob("/var/freeswitch/meetings/#{meeting_id}*.wav") ], :force => true # Remove all the recording flags - FileUtils.rm_f [ "#{recording_dir}/status/processed/#{meeting_id}-mconf.done", + FileUtils.rm_f [ "#{recording_dir}/status/processed/#{meeting_id}-mconf_encrypted.done", "#{recording_dir}/status/sanity/#{meeting_id}.done", "#{recording_dir}/status/recorded/#{meeting_id}.done", "#{recording_dir}/status/archived/#{meeting_id}.done" ] -- GitLab