-
Notifications
You must be signed in to change notification settings - Fork 0
Migration Guide
Version: 1.0 Last Updated: 2026-01-22
This guide covers migrating from AzuraCast and LibreTime to Grimnir Radio.
Grimnir Radio provides migration tools to help you transition from existing broadcast automation systems:
- AzuraCast: Import from backup tarball (.tar.gz)
- LibreTime: Import from PostgreSQL database
- Airtime: Use LibreTime importer (compatible)
- Quick Start
- AzuraCast Migration
- LibreTime/Airtime Migration
- API-Based Migration
- What Gets Imported
- Important Notes
- Troubleshooting
- Grimnir Radio installed and database configured
- For AzuraCast: Backup tarball file (.tar.gz)
- For LibreTime: Database credentials and network access
# Dry run (preview only)
grimnirradio import azuracast --backup /path/to/azuracast-backup.tar.gz --dry-run
# Full import
grimnirradio import azuracast --backup /path/to/azuracast-backup.tar.gz# Dry run (preview only)
grimnirradio import libretime \
--db-host localhost \
--db-user airtime \
--db-password secretpassword \
--dry-run
# Full import
grimnirradio import libretime \
--db-host localhost \
--db-user airtime \
--db-password secretpassword- Log into your AzuraCast instance
- Navigate to Administration → Backups
- Create a new backup or download an existing one
- Download the
.tar.gzfile to your Grimnir Radio server
grimnirradio import azuracast [OPTIONS]
Options:
--backup string Path to AzuraCast backup tarball (.tar.gz) (required)
--skip-media Skip media file import (stations/playlists only)
--dry-run Analyze backup without importinggrimnirradio import azuracast \
--backup /path/to/azuracast-backup-20260122.tar.gz \
--dry-runOutput:
Import Preview:
Stations: 3
Media: 1250
Playlists: 45
Run without --dry-run to perform the import.
grimnirradio import azuracast \
--backup /path/to/azuracast-backup-20260122.tar.gzgrimnirradio import azuracast \
--backup /path/to/azuracast-backup-20260122.tar.gz \
--skip-media| Item | Status | Notes |
|---|---|---|
| Stations | ✅ Complete | All station configurations |
| Mounts | Mount points created, GStreamer configs may need adjustment | |
| Media metadata | ✅ Complete | Title, artist, album, genre, duration |
| Media files | File paths stored in import_path, actual file copy not implemented yet |
|
| Playlists | Detected but import logic not fully implemented | |
| Schedules | ❌ Not implemented | |
| Users | ❌ Not implemented |
Ensure you have network access to the LibreTime PostgreSQL database:
# Test connection
psql -h localhost -U airtime -d airtime -c "SELECT version();"If connection fails:
- Check PostgreSQL is listening on network interface (edit
postgresql.conf) - Check
pg_hba.confallows connections from your IP - Restart PostgreSQL:
sudo systemctl restart postgresql
grimnirradio import libretime [OPTIONS]
Options:
--db-host string LibreTime database host (default "localhost")
--db-port int LibreTime database port (default 5432)
--db-name string LibreTime database name (default "airtime")
--db-user string LibreTime database user (required)
--db-password string LibreTime database password
--media-path string Path to LibreTime media directory
--skip-media Skip media file import
--skip-playlists Skip playlist import
--skip-schedules Skip schedule/show import
--dry-run Analyze database without importinggrimnirradio import libretime \
--db-host 192.168.1.100 \
--db-user airtime \
--db-password secretpass \
--dry-rungrimnirradio import libretime \
--db-host 192.168.1.100 \
--db-port 5432 \
--db-name airtime \
--db-user airtime \
--db-password secretpassgrimnirradio import libretime \
--db-host localhost \
--db-user airtime \
--db-password secretpass \
--skip-mediagrimnirradio import libretime \
--db-host localhost \
--db-user airtime \
--db-password secretpass \
--skip-schedules| Item | Status | Notes |
|---|---|---|
| Station configuration | ✅ Complete | From cc_pref table (station name, description) |
| Media metadata | ✅ Complete | From cc_files (title, artist, album, genre, duration, bitrate, etc.) |
| Media files | File paths stored in import_path, actual file copy not implemented yet |
|
| Playlists | ✅ Complete | From cc_playlist and cc_playlistcontents with fade settings |
| Shows | ✅ Complete | Imported as Clock templates from cc_show
|
| Show instances | Detected but schedule materialization not implemented | |
| Users | ❌ Not implemented |
LibreTime shows are imported as Clock templates in Grimnir Radio. This means:
- Each LibreTime show becomes a Grimnir Clock with the same name and duration
- Show content is NOT automatically imported (you'll need to rebuild show templates)
- Show schedules must be manually recreated in Grimnir Radio
Why this approach?
- LibreTime and Grimnir have different scheduling models
- LibreTime shows can contain complex logic that doesn't map 1:1
- Gives you flexibility to redesign your schedule in Grimnir's more flexible system
For automated workflows or web UI integration, use the REST API.
curl -X POST http://localhost:8080/api/v1/migrations \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_type": "azuracast",
"options": {
"azuracast_backup_path": "/path/to/backup.tar.gz",
"skip_media": false
}
}'Response:
{
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"source_type": "azuracast",
"status": "pending",
"dry_run": false,
"options": {...},
"progress": {...},
"created_at": "2026-01-22T10:00:00Z"
}
}curl -X POST http://localhost:8080/api/v1/migrations/{job_id}/start \
-H "Authorization: Bearer $TOKEN"curl http://localhost:8080/api/v1/migrations/{job_id} \
-H "Authorization: Bearer $TOKEN"Response:
{
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "running",
"progress": {
"phase": "importing_media",
"current_step": "Imported 500/1250 media files",
"total_steps": 5,
"completed_steps": 2,
"percentage": 40,
"media_total": 1250,
"media_imported": 500
}
}
}curl http://localhost:8080/api/v1/migrations \
-H "Authorization: Bearer $TOKEN"curl -X POST http://localhost:8080/api/v1/migrations/{job_id}/cancel \
-H "Authorization: Bearer $TOKEN"curl -X DELETE http://localhost:8080/api/v1/migrations/{job_id} \
-H "Authorization: Bearer $TOKEN"Connect to the WebSocket events endpoint and subscribe to migration events:
const ws = new WebSocket('ws://localhost:8080/api/v1/events?types=migration');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'migration') {
console.log('Job:', data.payload.job_id);
console.log('Status:', data.payload.status);
console.log('Progress:', data.payload.progress);
}
};- Station metadata: Name, description, timezone
- Media metadata: Title, artist, album, genre, year, track number, duration
- Playlist structures: Playlist names, descriptions, items, ordering
- Playlist item settings: Fade in/out durations, positions
-
Media files: File paths are imported to
import_pathfield, but actual file copying/moving is not yet implemented - Schedule templates: Shows/clocks are imported as templates, but schedule materialization is manual
- User accounts: Passwords cannot be migrated for security reasons
- Live DJ sessions: Historical data only
- Listener statistics: Historical data only
- Webstream relays: Configuration must be manually recreated
| Feature | Status | Notes |
|---|---|---|
| Multi-station setup | ✅ Complete | All stations imported |
| Custom fields | ❌ Not implemented | |
| Podcasts | ❌ Not implemented | |
| Remote relays | ❌ Not implemented | |
| Listener requests | ❌ Not implemented | Historical data only |
| Feature | Status | Notes |
|---|---|---|
| Show templates | ✅ Complete | Imported as Clocks |
| Smart blocks | Need manual recreation in Grimnir | |
| Podcast episodes | ❌ Not implemented | |
| Celery tasks | ❌ Not applicable | Different architecture |
- Always run a dry-run first to preview what will be imported
- Backup your Grimnir database before running a migration
- Verify imported data after migration completes
-
Media files are not automatically copied - file paths are stored in
import_pathfield
The migration system tracks ID mappings between source and destination systems:
{
"mappings": {
"station_1": {
"old_id": "1",
"new_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "station",
"name": "My Radio Station"
},
"media_42": {
"old_id": "42",
"new_id": "660e8400-e29b-41d4-a716-446655440001",
"type": "media",
"name": "Song Title.mp3"
}
}
}These mappings are stored in the migration job result and can be used for post-import processing.
After migration, you'll need to:
- Verify stations - Check station configurations match your requirements
- Media files - Copy media files to Grimnir's media directory (manual step)
-
Update file paths - Update
pathfield inmedia_itemstable to match new locations - Recreate schedules - Build new schedules using imported Clock templates
- Configure mounts - Adjust GStreamer encoder settings if needed
- Test playback - Verify audio output works correctly
- Create users - Manually create user accounts (passwords can't be migrated)
- Configure live inputs - Set up harbor/icecast live source endpoints
- Large libraries: Imports with >10,000 media items may take 30+ minutes
- Network speed: LibreTime import speed depends on database connection speed
- Database load: Consider running migrations during off-peak hours
-
Progress tracking: Use
--dry-runfirst to estimate total time
Problem: AzuraCast backup file not found
Solutions:
- Check file path is absolute (not relative)
- Verify file exists:
ls -lh /path/to/backup.tar.gz - Check file permissions:
chmod 644 /path/to/backup.tar.gz
Problem: Cannot connect to LibreTime PostgreSQL database
Solutions:
- Test connection manually:
psql -h hostname -U airtime -d airtime -c "SELECT version();" - Check PostgreSQL is listening on network:
sudo netstat -an | grep 5432 - Edit
/etc/postgresql/*/main/postgresql.conf:listen_addresses = '*' - Edit
/etc/postgresql/*/main/pg_hba.conf:host airtime airtime 0.0.0.0/0 md5 - Restart PostgreSQL:
sudo systemctl restart postgresql
Problem: AzuraCast backup is wrong format
Solutions:
- Ensure backup was downloaded from AzuraCast (not another system)
- Check file extension is
.tar.gz - Verify file is valid:
tar -tzf backup.tar.gz | head
Problem: Migration job ID doesn't exist
Solutions:
- List all jobs:
curl http://localhost:8080/api/v1/migrations - Check job ID is correct (UUID format)
- Job may have been deleted after completion
Warning: This is expected behavior
Explanation:
- Media metadata is imported (title, artist, etc.)
- File paths are stored in
import_pathfield - Actual file copying must be done manually
Manual file migration:
# Example: Copy LibreTime media to Grimnir
rsync -av /srv/airtime/stor/ /var/lib/grimnir/media/
# Update database paths (example)
psql -d grimnir -c "
UPDATE media_items
SET path = '/var/lib/grimnir/media/' || SUBSTRING(import_path FROM '[^/]*$')
WHERE import_path IS NOT NULL;
"Problem: Import validation errors
Solutions:
- Read validation error messages carefully
- For AzuraCast: Ensure backup file is valid and not corrupted
- For LibreTime: Verify database credentials and network access
- Run dry-run to see detailed validation results
Problem: Import stops with error mid-process
Recovery steps:
- Check logs for error details:
grep ERROR /var/log/grimnir/grimnir-radio.log | tail -50 - Fix the underlying issue (disk space, permissions, etc.)
- Delete the partial import:
-- Identify stations created by failed import SELECT * FROM stations WHERE created_at > '2026-01-22 10:00:00'; -- Delete if needed (use with caution!) DELETE FROM stations WHERE id = 'station-uuid';
- Re-run import
For migration issues:
-
Check logs:
/var/log/grimnir/grimnir-radio.log - GitHub Issues: https://github.com/friendsincode/grimnir_radio/issues
- Community Forum: https://community.grimnir.radio
After successful migration:
- Read Multi-Instance for scaling guidance
- Review Observability for monitoring setup
- Check Production Deployment for deployment best practices
Getting Started
Core Concepts
Deployment
Integration
Operations
Development
Reference