Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Improved error handling when a room requires an access code but none was provided ([#3035])
- GL2- / GL3-Import commands now prepare import of existing recordings ([#2877], ([#3034])

## [v4.14.2] - 2026-04-10

Expand Down Expand Up @@ -756,6 +757,7 @@ You can find the changelog for older versions there [here](https://github.com/TH
[#2857]: https://github.com/THM-Health/PILOS/pull/2857
[#2873]: https://github.com/THM-Health/PILOS/issues/2873
[#2874]: https://github.com/THM-Health/PILOS/pull/2874
[#2877]: https://github.com/THM-Health/PILOS/issues/2877
[#2879]: https://github.com/THM-Health/PILOS/issues/2879
[#2880]: https://github.com/THM-Health/PILOS/pull/2880
[#2901]: https://github.com/THM-Health/PILOS/pull/2901
Expand All @@ -773,6 +775,7 @@ You can find the changelog for older versions there [here](https://github.com/TH
[#3014]: https://github.com/THM-Health/PILOS/pull/3014
[#3028]: https://github.com/THM-Health/PILOS/issues/3028
[#3029]: https://github.com/THM-Health/PILOS/pull/3029
[#3034]: https://github.com/THM-Health/PILOS/pull/3034
[#3035]: https://github.com/THM-Health/PILOS/pull/3035
[#3039]: https://github.com/THM-Health/PILOS/issues/3039
[#3040]: https://github.com/THM-Health/PILOS/pull/3040
Expand Down
9 changes: 8 additions & 1 deletion app/Console/Commands/ImportGreenlight2Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Enums\RoomLobby;
use App\Enums\RoomUserRole;
use App\Models\Meeting;
use App\Models\Role;
use App\Models\Room;
use App\Models\RoomFile;
Expand Down Expand Up @@ -126,7 +127,7 @@ public function handle()

$requireAuth = DB::connection('greenlight')->table('features')->where('name', 'Room Authentication')->first('value')?->value == true;
$users = DB::connection('greenlight')->table('users')->where('deleted', false)->get(['id', 'provider', 'username', 'social_uid', 'email', 'name', 'password_digest']);
$rooms = DB::connection('greenlight')->table('rooms')->where('deleted', false)->get(['id', 'uid', 'user_id', 'name', 'room_settings', 'access_code']);
$rooms = DB::connection('greenlight')->table('rooms')->where('deleted', false)->get(['id', 'uid', 'bbb_id', 'user_id', 'name', 'room_settings', 'access_code']);
$sharedAccesses = DB::connection('greenlight')->table('shared_accesses')->get(['room_id', 'user_id']);

$socialProviders = DB::connection('greenlight')->table('users')->select('provider')->whereNotIn('provider', ['greenlight', 'ldap'])->distinct()->get();
Expand Down Expand Up @@ -387,6 +388,12 @@ protected function importRooms(Collection $rooms, int $roomType, array $userMap,
$dbRoom->roomType()->associate($roomType);
$dbRoom->save();

// Create meeting
$dbMeeting = new Meeting;
$dbMeeting->id = $room->bbb_id;
$dbMeeting->room()->associate($dbRoom);
$dbMeeting->save();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// increase counter and add room to room map (key = greenlight db id, value = new db id)
$created++;
$roomMap[$room->id] = $room->uid;
Expand Down
9 changes: 8 additions & 1 deletion app/Console/Commands/ImportGreenlight3Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Enums\RoomLobby;
use App\Enums\RoomUserRole;
use App\Models\Meeting;
use App\Models\Role;
use App\Models\Room;
use App\Models\RoomFile;
Expand Down Expand Up @@ -108,7 +109,7 @@ public function handle()
]]);

$users = DB::connection('greenlight')->table('users')->where('provider', 'greenlight')->get(['id', 'name', 'email', 'external_id', 'password_digest']);
$rooms = DB::connection('greenlight')->table('rooms')->get(['id', 'friendly_id', 'user_id', 'name']);
$rooms = DB::connection('greenlight')->table('rooms')->get(['id', 'friendly_id', 'meeting_id', 'user_id', 'name']);
$sharedAccesses = DB::connection('greenlight')->table('shared_accesses')->get(['room_id', 'user_id']);

// Start transaction to rollback if import fails or user cancels
Expand Down Expand Up @@ -301,6 +302,12 @@ protected function importRooms(Collection $rooms, int $roomType, array $userMap,
$dbRoom->roomType()->associate($roomType);
$dbRoom->save();

// Create meeting
$dbMeeting = new Meeting;
$dbMeeting->id = $room->meeting_id;
$dbMeeting->room()->associate($dbRoom);
$dbMeeting->save();
Comment thread
samuelwei marked this conversation as resolved.

// increase counter and add room to room map (key = greenlight db id, value = new db id)
$created++;
$roomMap[$room->id] = $room->friendly_id;
Expand Down
22 changes: 7 additions & 15 deletions app/Models/RecordingFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Date;
use SimpleXMLElement;
Expand Down Expand Up @@ -44,20 +45,11 @@ public static function createFromRecordingXML(SimpleXMLElement $xml)
$meetingId = (string) $xml->meta->meetingId;
$meetingName = (string) $xml->meta->meetingName;

// Find the meeting with the given id
$meeting = Meeting::where('id', $meetingId)->first();

// If the meeting exists, get the room from the meeting
// Otherwise, find the room with the given id
if ($meeting != null) {
$room = $meeting->room;
} else {
// Fallback to greenlight behaviour (using the persistent room id as meeting id)
$room = Room::where('id', $meetingId)->first();
}

// If the room does not exist, we can't associate the recording format with a room
if ($room == null) {
try {
// Find the meeting with the given id
$meeting = Meeting::findOrFail($meetingId);
} catch (ModelNotFoundException) {
// If the meeting does not exist, we can't associate the recording format with anything
return null;
}

Expand All @@ -69,7 +61,7 @@ public static function createFromRecordingXML(SimpleXMLElement $xml)
$recording->description = $meetingName;
$recording->start = $start;
$recording->end = $end;
$recording->room()->associate($room);
$recording->room()->associate($meeting->room);
$recording->meeting()->associate($meeting);
$recording->save();
}
Expand Down
25 changes: 22 additions & 3 deletions docs/docs/administration/08-advanced/06-migrate-greenlight.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Migrate from Greenlight
description: Step by step guide to migrate from Greenlight to PILOS
---

PILOS provides an easy to use command to import all Greenlight users (incl. ldap), rooms and shared accesses.
PILOS provides an easy to use command to import all Greenlight users (incl. ldap), rooms (incl. default presentation) and shared accesses.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix compound adjective hyphenation.

The phrase "easy to use" should be hyphenated when used as a compound adjective before a noun.

📝 Proposed grammar fix
-PILOS provides an easy to use command to import all Greenlight users (incl. ldap), rooms (incl. default presentation) and shared accesses.
+PILOS provides an easy-to-use command to import all Greenlight users (incl. ldap), rooms (incl. default presentation) and shared accesses.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PILOS provides an easy to use command to import all Greenlight users (incl. ldap), rooms (incl. default presentation) and shared accesses.
PILOS provides an easy-to-use command to import all Greenlight users (incl. ldap), rooms (incl. default presentation) and shared accesses.
🧰 Tools
🪛 LanguageTool

[grammar] ~6-~6: Use a hyphen to join words.
Context: ...ght to PILOS --- PILOS provides an easy to use command to import all Greenlight use...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/administration/08-advanced/06-migrate-greenlight.md` at line 6,
Replace the unhyphenated compound adjective "easy to use" with the hyphenated
form "easy-to-use" in the sentence that reads "PILOS provides an easy to use
command to import all Greenlight users (incl. ldap), rooms (incl. default
presentation) and shared accesses." to correctly form the compound adjective
before the noun; update only that phrase ("easy to use" → "easy-to-use")
preserving the rest of the sentence and punctuation.


## Preparing migration from other host

Expand Down Expand Up @@ -31,9 +31,9 @@ Successfully imported presentation files will be copied to a different location.

## Running migration command

The command will output the process of the import and informs about failed user, room and shared access import.
The command will output the process of the import and informs about failed user, room, room presentation and shared access import.

**Note** If a room with the same room id already exists in PILOS it will NOT be imported and the shared accesses are ignored.
**Note** If a room with the same room id already exists in PILOS it will NOT be imported and its default presentation and shared accesses are ignored.

### Greenlight 2

Expand Down Expand Up @@ -135,6 +135,25 @@ docker compose exec app pilos-cli import:greenlight-v3 \
12345678
```

## Importing recordings

You can also import recordings for existing rooms. To make this possible the import command creates a meeting with the BBB meeting ID for every imported room.
This meeting does not have a start- or end timestamp, so it not visible in the frontend. Associated recordings _will_ be listed, however.

To import existing recordings, you have to

1. find the meeting ID (column `bbb_id` (GL2) or `meeting_id` (GL3) in the `rooms` table)
2. find all recordings with this meeting ID (XPath: `/recording/meta/meetingId` in metadata.xml)
3. pack matching recordings into tar files and
4. move or copy those tar files to PILOS' `recordings-spool` directory.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Existing recordings prepared like this will be imported just like new ones would.

**Note** You _may_ have to temporarily increase RAM allocated to horizon if it is limited.

**Note** YMMV, depending on your BBB loadbalancer. If, for example, you run b3scale, you need to extract the "simple" Greenlight meeting ID from the
more complex b3scale meeting ID.

## Adjust nginx

### PILOS is running on the same host
Expand Down
59 changes: 32 additions & 27 deletions tests/Backend/Unit/Console/ImportGreenlight2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Enums\RoomLobby;
use App\Enums\RoomUserRole;
use App\Models\Meeting;
use App\Models\Role;
use App\Models\Room;
use App\Models\RoomType;
Expand Down Expand Up @@ -99,7 +100,7 @@ private function fakeDatabase(bool $roomAuth, Collection $users, Collection $roo
->with('deleted', false)
->andReturn(Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) use ($rooms) {
$mock->shouldReceive('get')
->with(['id', 'uid', 'user_id', 'name', 'room_settings', 'access_code'])
->with(['id', 'uid', 'bbb_id', 'user_id', 'name', 'room_settings', 'access_code'])
->andReturn($rooms);
}));
}));
Expand Down Expand Up @@ -238,17 +239,17 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, boo

// Create fake rooms
$rooms = [];
$rooms[] = new Greenlight2Room(1, $users[0]->id, 'Test Room 1', 'abc-def-xyz-123');
$rooms[] = new Greenlight2Room(2, $users[1]->id, 'Test Room 2', 'abc-def-xyz-234');
$rooms[] = new Greenlight2Room(3, $users[2]->id, 'Test Room 3', 'abc-def-xyz-345');
$rooms[] = new Greenlight2Room(4, $users[3]->id, 'Test Room 4', 'abc-def-xyz-456');
$rooms[] = new Greenlight2Room(5, $users[4]->id, 'Test Room 5', 'abc-def-xyz-567');
$rooms[] = new Greenlight2Room(6, $users[5]->id, 'Test Room 6', 'abc-def-xyz-678');

$rooms[] = new Greenlight2Room(7, $users[0]->id, 'Test Room 7', 'hij-klm-xyz-123', '012345', ['muteOnStart' => false, 'requireModeratorApproval' => true, 'anyoneCanStart' => false, 'joinModerator' => true]);
$rooms[] = new Greenlight2Room(8, $users[0]->id, 'Test Room 8', 'hij-klm-xyz-234', null, ['muteOnStart' => true, 'requireModeratorApproval' => false, 'anyoneCanStart' => true, 'joinModerator' => false]);
$rooms[] = new Greenlight2Room(9, 99, 'Test Room 9', 'hij-klm-xyz-456', '012345');
$rooms[] = new Greenlight2Room(10, $users[0]->id, 'Test Room 10', $existingRoom->id);
$rooms[] = new Greenlight2Room(1, 'abc-def-xyz-123', 'thue5aivaiyohreetu1zaipe4iez7eengoopoohi', 'Test Room 1', $users[0]->id);
$rooms[] = new Greenlight2Room(2, 'abc-def-xyz-234', 'aef9eiquoo8oph9oon4oovit8oid9ree7caigahw', 'Test Room 2', $users[1]->id);
$rooms[] = new Greenlight2Room(3, 'abc-def-xyz-345', 'iekoongaicahhaivah0xaud3tha3iem8eathoaxu', 'Test Room 3', $users[2]->id);
$rooms[] = new Greenlight2Room(4, 'abc-def-xyz-456', 'oeph7ohhoochoy7ruoshae9uephie0tha8aup3oo', 'Test Room 4', $users[3]->id);
$rooms[] = new Greenlight2Room(5, 'abc-def-xyz-567', 'eecae0kugoo3aes2aiquaif8aeraiy1aiva2ohje', 'Test Room 5', $users[4]->id);
$rooms[] = new Greenlight2Room(6, 'abc-def-xyz-678', 'eizaipaikohheizohvaehiech5ach3el9haech5g', 'Test Room 6', $users[5]->id);

$rooms[] = new Greenlight2Room(7, 'hij-klm-xyz-123', 'aebaoj6phak4eaghoizeiwaecudei6hishochua7', 'Test Room 7', $users[0]->id, '012345', ['muteOnStart' => false, 'requireModeratorApproval' => true, 'anyoneCanStart' => false, 'joinModerator' => true]);
$rooms[] = new Greenlight2Room(8, 'hij-klm-xyz-234', 'aicha2vahw1zei7aecoo1ainoph1ietei3nei4la', 'Test Room 8', $users[0]->id, null, ['muteOnStart' => true, 'requireModeratorApproval' => false, 'anyoneCanStart' => true, 'joinModerator' => false]);
$rooms[] = new Greenlight2Room(9, 'hij-klm-xyz-456', 'quohseseey2aheicoc3eedaedei4kif8zo4xaiki', 'Test Room 9', 99, '012345');
$rooms[] = new Greenlight2Room(10, $existingRoom->id, 'aima0eiv6uyaif6ien8ahchoothohkeiphaegh0u', 'Test Room 10', $users[0]->id);

// Create fake presentations
$presentations = [];
Expand Down Expand Up @@ -286,6 +287,7 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, boo

// check amount of rooms and users
$this->assertCount(9, Room::all());
$this->assertCount(8, Meeting::all());
$this->assertCount(2, User::where('authenticator', 'local')->get());
$this->assertCount(2, User::where('authenticator', 'ldap')->get());
$this->assertCount(1, User::where('authenticator', 'shibboleth')->get());
Expand All @@ -297,6 +299,21 @@ protected function test_helper_full(array $cmdOptions, callable $expectHook, boo
Room::all()->pluck('id')->toArray()
);

// check if all meetings are created
$this->assertEqualsCanonicalizing(
[
'thue5aivaiyohreetu1zaipe4iez7eengoopoohi',
'aef9eiquoo8oph9oon4oovit8oid9ree7caigahw',
'iekoongaicahhaivah0xaud3tha3iem8eathoaxu',
'oeph7ohhoochoy7ruoshae9uephie0tha8aup3oo',
'eecae0kugoo3aes2aiquaif8aeraiy1aiva2ohje',
'eizaipaikohheizohvaehiech5ach3el9haech5g',
'aebaoj6phak4eaghoizeiwaecudei6hishochua7',
'aicha2vahw1zei7aecoo1ainoph1ietei3nei4la',
],
Meeting::all()->pluck('id')->toArray()
);

// check if allow guest setting is correct
foreach (Room::where('id', '!=', $existingRoom->id)->get() as $room) {
$this->assertEquals(! $roomAuth, $room->allow_guests);
Expand Down Expand Up @@ -398,11 +415,7 @@ public function test_interactive()
->expectsOutput('Importing rooms')
->expectsOutput('8 created, 1 skipped (already existed)')
->expectsOutput('Room import failed for the following 1 rooms, because no room owner was found:')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsOutput('| Name | ID | Access code |')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsOutput('| Test Room 9 | hij-klm-xyz-456 | 012345 |')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsTable(['Name', 'ID', 'Access code'], [['Test Room 9', 'hij-klm-xyz-456', '012345']])
->expectsOutput('Importing presentations for rooms')
->expectsOutput('2 created, 1 skipped (file not found)')
->expectsOutput('Importing shared room accesses')
Expand Down Expand Up @@ -430,11 +443,7 @@ public function test_non_interactive()
->expectsOutput('Importing rooms')
->expectsOutput('8 created, 1 skipped (already existed)')
->expectsOutput('Room import failed for the following 1 rooms, because no room owner was found:')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsOutput('| Name | ID | Access code |')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsOutput('| Test Room 9 | hij-klm-xyz-456 | 012345 |')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsTable(['Name', 'ID', 'Access code'], [['Test Room 9', 'hij-klm-xyz-456', '012345']])
->expectsOutput('Importing presentations for rooms')
->expectsOutput('2 created, 1 skipped (file not found)')
->expectsOutput('Importing shared room accesses')
Expand Down Expand Up @@ -478,11 +487,7 @@ public function test_rollback()
->expectsOutput('Importing rooms')
->expectsOutput('8 created, 1 skipped (already existed)')
->expectsOutput('Room import failed for the following 1 rooms, because no room owner was found:')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsOutput('| Name | ID | Access code |')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsOutput('| Test Room 9 | hij-klm-xyz-456 | 012345 |')
->expectsOutput('+-------------+-----------------+-------------+')
->expectsTable(['Name', 'ID', 'Access code'], [['Test Room 9', 'hij-klm-xyz-456', '012345']])
->expectsOutput('Importing presentations for rooms')
->expectsOutput('2 created, 1 skipped (file not found)')
->expectsOutput('Importing shared room accesses')
Expand Down
Loading
Loading