feat: initial implementation of metadata aggregator
- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,922 @@
|
||||
# Melodee: Deployment Analysis
|
||||
|
||||
## Deployment Strategy Overview
|
||||
|
||||
Melodee provides Docker-based deployment with multi-stage builds, Docker Compose orchestration, and automatic database migrations. The deployment architecture prioritizes ease of setup for self-hosted environments while supporting advanced configurations for production deployments.
|
||||
|
||||
Key deployment features:
|
||||
- **Docker multi-stage build**: Optimized image size and security
|
||||
- **Docker Compose**: Single-command deployment with PostgreSQL
|
||||
- **Automatic migrations**: Database schema updates on container startup
|
||||
- **12 persistent volumes**: Data persistence across container restarts
|
||||
- **Raspberry Pi support**: ARM64 compatibility for low-power hardware
|
||||
- **Podman compatibility**: Rootless container runtime support
|
||||
|
||||
## Docker Architecture
|
||||
|
||||
### Multi-Stage Dockerfile
|
||||
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Copy project files
|
||||
COPY ["Melodee.Web/Melodee.Web.csproj", "Melodee.Web/"]
|
||||
COPY ["Melodee.Data/Melodee.Data.csproj", "Melodee.Data/"]
|
||||
COPY ["Melodee.Core/Melodee.Core.csproj", "Melodee.Core/"]
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore "Melodee.Web/Melodee.Web.csproj"
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build application
|
||||
WORKDIR "/src/Melodee.Web"
|
||||
RUN dotnet build "Melodee.Web.csproj" -c Release -o /app/build
|
||||
|
||||
# Publish application
|
||||
RUN dotnet publish "Melodee.Web.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# Runtime stage
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Install FFmpeg for transcoding
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends ffmpeg && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy published application
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5000
|
||||
|
||||
# Set entrypoint
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
```
|
||||
|
||||
**Multi-Stage Benefits**:
|
||||
1. **Smaller image size**: Runtime image excludes SDK (saves ~500 MB)
|
||||
2. **Faster deployments**: Smaller images transfer and start faster
|
||||
3. **Security**: No build tools in production image
|
||||
4. **Layer caching**: Dependencies cached separately from source code
|
||||
|
||||
**Image Size Comparison**:
|
||||
- Single-stage (with SDK): ~1.2 GB
|
||||
- Multi-stage (runtime only): ~700 MB
|
||||
- Savings: ~500 MB (42% reduction)
|
||||
|
||||
### Entrypoint Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Melodee v1.8.0 starting..."
|
||||
|
||||
# Wait for PostgreSQL to be ready
|
||||
echo "Waiting for PostgreSQL..."
|
||||
until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c '\q' 2>/dev/null; do
|
||||
echo "PostgreSQL is unavailable - sleeping"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "PostgreSQL is ready"
|
||||
|
||||
# Run database migrations
|
||||
echo "Applying database migrations..."
|
||||
dotnet ef database update --project /app/Melodee.Data.dll --no-build
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Migration failed, exiting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Migrations applied successfully"
|
||||
|
||||
# Start application
|
||||
echo "Starting Melodee..."
|
||||
exec dotnet Melodee.Web.dll
|
||||
```
|
||||
|
||||
**Entrypoint Responsibilities**:
|
||||
1. **Database readiness check**: Waits for PostgreSQL before starting
|
||||
2. **Automatic migrations**: Applies schema changes on startup
|
||||
3. **Error handling**: Exits if migrations fail
|
||||
4. **Process replacement**: `exec` replaces shell with .NET process for proper signal handling
|
||||
|
||||
**Signal Handling**:
|
||||
The `exec` command is critical for graceful shutdown. Without it:
|
||||
- Docker sends SIGTERM to shell process
|
||||
- Shell doesn't forward signal to .NET process
|
||||
- .NET process killed with SIGKILL after timeout
|
||||
- No graceful shutdown (connections dropped, jobs interrupted)
|
||||
|
||||
With `exec`:
|
||||
- Docker sends SIGTERM directly to .NET process
|
||||
- .NET process handles shutdown gracefully
|
||||
- Connections closed cleanly
|
||||
- Background jobs complete or checkpoint
|
||||
|
||||
### Docker Compose Configuration
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
melodee:
|
||||
image: melodee:1.8.0
|
||||
container_name: melodee
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5000:5000"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- ASPNETCORE_URLS=http://+:5000
|
||||
- ConnectionStrings__DefaultConnection=Host=postgres;Database=melodee;Username=melodee;Password=${POSTGRES_PASSWORD}
|
||||
- MusicBrainz__CachePath=/data/mb-cache.db
|
||||
- Library__Path=/music
|
||||
- Spotify__ClientId=${SPOTIFY_CLIENT_ID}
|
||||
- Spotify__ClientSecret=${SPOTIFY_CLIENT_SECRET}
|
||||
- LastFm__ApiKey=${LASTFM_API_KEY}
|
||||
- LastFm__SharedSecret=${LASTFM_SHARED_SECRET}
|
||||
- Google__ClientId=${GOOGLE_CLIENT_ID}
|
||||
- Google__ClientSecret=${GOOGLE_CLIENT_SECRET}
|
||||
- Brave__ApiKey=${BRAVE_API_KEY}
|
||||
volumes:
|
||||
- music:/music
|
||||
- data:/data
|
||||
- logs:/var/log/melodee
|
||||
- config:/app/config
|
||||
- cache:/app/cache
|
||||
- album-art:/app/album-art
|
||||
- transcoding:/app/transcoding
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- melodee-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
postgres:
|
||||
image: postgres:17
|
||||
container_name: melodee-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=melodee
|
||||
- POSTGRES_USER=melodee
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
- postgres-backups:/backups
|
||||
networks:
|
||||
- melodee-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U melodee"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
music:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: /path/to/music/library
|
||||
data:
|
||||
driver: local
|
||||
logs:
|
||||
driver: local
|
||||
config:
|
||||
driver: local
|
||||
cache:
|
||||
driver: local
|
||||
album-art:
|
||||
driver: local
|
||||
transcoding:
|
||||
driver: local
|
||||
postgres-data:
|
||||
driver: local
|
||||
postgres-backups:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
melodee-network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
**Volume Breakdown**:
|
||||
|
||||
| Volume | Purpose | Size | Backup Priority |
|
||||
|--------|---------|------|-----------------|
|
||||
| `music` | User's music library | Varies (100GB-10TB) | Critical (user data) |
|
||||
| `data` | MusicBrainz cache, app data | 2-5 GB | Medium (rebuildable) |
|
||||
| `logs` | Application logs | 1-10 GB | Low (rotated) |
|
||||
| `config` | User settings, API keys | <1 MB | Critical (secrets) |
|
||||
| `cache` | Metadata cache | 100 MB-1 GB | Low (rebuildable) |
|
||||
| `album-art` | Album cover images | 1-10 GB | Medium (re-downloadable) |
|
||||
| `transcoding` | Temporary transcoded files | 1-5 GB | None (temporary) |
|
||||
| `postgres-data` | PostgreSQL database | 1-10 GB | Critical (user data) |
|
||||
| `postgres-backups` | Database backups | 5-50 GB | Critical (disaster recovery) |
|
||||
|
||||
**Environment Variables**:
|
||||
|
||||
| Variable | Purpose | Required | Default |
|
||||
|----------|---------|----------|---------|
|
||||
| `ASPNETCORE_ENVIRONMENT` | Runtime environment | No | Production |
|
||||
| `ASPNETCORE_URLS` | Listening URLs | No | http://+:5000 |
|
||||
| `ConnectionStrings__DefaultConnection` | PostgreSQL connection | Yes | - |
|
||||
| `MusicBrainz__CachePath` | SQLite cache location | No | /data/mb-cache.db |
|
||||
| `Library__Path` | Music library path | Yes | - |
|
||||
| `Spotify__ClientId` | Spotify API credentials | No | - |
|
||||
| `Spotify__ClientSecret` | Spotify API credentials | No | - |
|
||||
| `LastFm__ApiKey` | Last.fm API credentials | No | - |
|
||||
| `LastFm__SharedSecret` | Last.fm API credentials | No | - |
|
||||
| `Google__ClientId` | Google OAuth credentials | No | - |
|
||||
| `Google__ClientSecret` | Google OAuth credentials | No | - |
|
||||
| `Brave__ApiKey` | Brave Search API key | No | - |
|
||||
|
||||
**Health Checks**:
|
||||
- **Melodee**: HTTP GET to `/health` endpoint every 30 seconds
|
||||
- **PostgreSQL**: `pg_isready` command every 10 seconds
|
||||
|
||||
Health checks enable:
|
||||
- **Automatic restarts**: Container restarts if unhealthy
|
||||
- **Load balancer integration**: Remove unhealthy instances from rotation
|
||||
- **Monitoring alerts**: Trigger notifications on health check failures
|
||||
|
||||
### Environment File (.env)
|
||||
|
||||
```bash
|
||||
# PostgreSQL
|
||||
POSTGRES_PASSWORD=your-secure-password
|
||||
|
||||
# Spotify (optional)
|
||||
SPOTIFY_CLIENT_ID=your-spotify-client-id
|
||||
SPOTIFY_CLIENT_SECRET=your-spotify-client-secret
|
||||
|
||||
# Last.fm (optional)
|
||||
LASTFM_API_KEY=your-lastfm-api-key
|
||||
LASTFM_SHARED_SECRET=your-lastfm-shared-secret
|
||||
|
||||
# Google OAuth (optional)
|
||||
GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
|
||||
# Brave Search (optional)
|
||||
BRAVE_API_KEY=your-brave-api-key
|
||||
```
|
||||
|
||||
**Security Considerations**:
|
||||
- `.env` file should be in `.gitignore`
|
||||
- Use strong passwords (20+ characters, mixed case, numbers, symbols)
|
||||
- Rotate API keys periodically
|
||||
- Restrict file permissions: `chmod 600 .env`
|
||||
|
||||
## Deployment Scenarios
|
||||
|
||||
### Single-Server Deployment
|
||||
|
||||
**Hardware Requirements**:
|
||||
- **CPU**: 2+ cores (4+ recommended)
|
||||
- **RAM**: 4 GB minimum (8 GB recommended)
|
||||
- **Storage**: 50 GB minimum (varies with library size)
|
||||
- **Network**: 100 Mbps+ for streaming
|
||||
|
||||
**Deployment Steps**:
|
||||
|
||||
1. **Install Docker and Docker Compose**:
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker.io docker-compose
|
||||
|
||||
# Enable Docker service
|
||||
sudo systemctl enable docker
|
||||
sudo systemctl start docker
|
||||
```
|
||||
|
||||
2. **Clone repository or create docker-compose.yml**:
|
||||
```bash
|
||||
mkdir melodee
|
||||
cd melodee
|
||||
# Create docker-compose.yml and .env files
|
||||
```
|
||||
|
||||
3. **Configure environment variables**:
|
||||
```bash
|
||||
nano .env
|
||||
# Set POSTGRES_PASSWORD and optional API keys
|
||||
```
|
||||
|
||||
4. **Update music library path**:
|
||||
```bash
|
||||
# Edit docker-compose.yml
|
||||
# Change device: /path/to/music/library to actual path
|
||||
```
|
||||
|
||||
5. **Start services**:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
6. **Verify deployment**:
|
||||
```bash
|
||||
docker-compose ps
|
||||
docker-compose logs -f melodee
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
7. **Access web interface**:
|
||||
```
|
||||
http://localhost:5000
|
||||
```
|
||||
|
||||
### Raspberry Pi Deployment
|
||||
|
||||
**Hardware Requirements**:
|
||||
- **Model**: Raspberry Pi 4 (4GB+ RAM recommended)
|
||||
- **Storage**: 64 GB+ microSD or USB SSD
|
||||
- **OS**: Raspberry Pi OS 64-bit or Ubuntu Server ARM64
|
||||
|
||||
**ARM64 Image Build**:
|
||||
```dockerfile
|
||||
# Use ARM64 base images
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0-arm64v8 AS build
|
||||
# ... build stage ...
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0-arm64v8 AS runtime
|
||||
# ... runtime stage ...
|
||||
```
|
||||
|
||||
**Performance Optimizations**:
|
||||
1. **Use SSD instead of microSD**: 10x faster I/O
|
||||
2. **Disable transcoding**: Use direct streaming when possible
|
||||
3. **Limit concurrent jobs**: Reduce background job parallelism
|
||||
4. **Increase swap**: Add 2-4 GB swap for memory-intensive operations
|
||||
|
||||
**Deployment Steps**:
|
||||
```bash
|
||||
# Install Docker
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
|
||||
# Add user to docker group
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Install Docker Compose
|
||||
sudo apt-get install -y docker-compose
|
||||
|
||||
# Deploy Melodee
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**Resource Limits**:
|
||||
```yaml
|
||||
services:
|
||||
melodee:
|
||||
# ... other config ...
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '3'
|
||||
memory: 3G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
```
|
||||
|
||||
### Reverse Proxy Deployment
|
||||
|
||||
**Nginx Configuration**:
|
||||
```nginx
|
||||
upstream melodee {
|
||||
server localhost:5000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name music.example.com;
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name music.example.com;
|
||||
|
||||
# SSL certificates
|
||||
ssl_certificate /etc/letsencrypt/live/music.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/music.example.com/privkey.pem;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Proxy settings
|
||||
location / {
|
||||
proxy_pass http://melodee;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Timeouts for streaming
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
# Increase max upload size for album art
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
```
|
||||
|
||||
**Traefik Configuration** (Docker labels):
|
||||
```yaml
|
||||
services:
|
||||
melodee:
|
||||
# ... other config ...
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.melodee.rule=Host(`music.example.com`)"
|
||||
- "traefik.http.routers.melodee.entrypoints=websecure"
|
||||
- "traefik.http.routers.melodee.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.melodee.loadbalancer.server.port=5000"
|
||||
```
|
||||
|
||||
### High Availability Deployment
|
||||
|
||||
**Architecture**:
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Load Balancer│
|
||||
│ (HAProxy) │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌──────────────────┼──────────────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||||
│Melodee 1│ │Melodee 2│ │Melodee 3│
|
||||
└────┬────┘ └────┬────┘ └────┬────┘
|
||||
│ │ │
|
||||
└──────────────────┼──────────────────┘
|
||||
│
|
||||
┌──────▼───────┐
|
||||
│ PostgreSQL │
|
||||
│ Primary │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌──────▼───────┐
|
||||
│ PostgreSQL │
|
||||
│ Replica │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
**Challenges**:
|
||||
1. **Blazor Server state**: SignalR connections tied to specific server
|
||||
2. **Session affinity**: Load balancer must route user to same server
|
||||
3. **Shared storage**: Music library and album art must be accessible to all instances
|
||||
|
||||
**Solutions**:
|
||||
|
||||
**1. Redis Backplane for SignalR**:
|
||||
```csharp
|
||||
services.AddSignalR()
|
||||
.AddStackExchangeRedis(options =>
|
||||
{
|
||||
options.Configuration.EndPoints.Add("redis:6379");
|
||||
});
|
||||
```
|
||||
|
||||
**2. HAProxy Sticky Sessions**:
|
||||
```
|
||||
backend melodee
|
||||
balance roundrobin
|
||||
cookie SERVERID insert indirect nocache
|
||||
server melodee1 melodee1:5000 check cookie melodee1
|
||||
server melodee2 melodee2:5000 check cookie melodee2
|
||||
server melodee3 melodee3:5000 check cookie melodee3
|
||||
```
|
||||
|
||||
**3. NFS for Shared Storage**:
|
||||
```yaml
|
||||
volumes:
|
||||
music:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: nfs
|
||||
o: addr=nfs-server,rw
|
||||
device: ":/music"
|
||||
album-art:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: nfs
|
||||
o: addr=nfs-server,rw
|
||||
device: ":/album-art"
|
||||
```
|
||||
|
||||
**4. PostgreSQL Replication**:
|
||||
```yaml
|
||||
services:
|
||||
postgres-primary:
|
||||
image: postgres:17
|
||||
environment:
|
||||
- POSTGRES_REPLICATION_MODE=master
|
||||
- POSTGRES_REPLICATION_USER=replicator
|
||||
- POSTGRES_REPLICATION_PASSWORD=replicator-password
|
||||
volumes:
|
||||
- postgres-primary-data:/var/lib/postgresql/data
|
||||
|
||||
postgres-replica:
|
||||
image: postgres:17
|
||||
environment:
|
||||
- POSTGRES_REPLICATION_MODE=slave
|
||||
- POSTGRES_MASTER_HOST=postgres-primary
|
||||
- POSTGRES_REPLICATION_USER=replicator
|
||||
- POSTGRES_REPLICATION_PASSWORD=replicator-password
|
||||
volumes:
|
||||
- postgres-replica-data:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
## Podman Deployment
|
||||
|
||||
Podman is a daemonless, rootless container runtime compatible with Docker.
|
||||
|
||||
**Advantages**:
|
||||
- **Rootless**: Runs without root privileges
|
||||
- **Daemonless**: No background daemon process
|
||||
- **Systemd integration**: Native systemd service generation
|
||||
|
||||
**Deployment Steps**:
|
||||
|
||||
1. **Install Podman**:
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install -y podman podman-compose
|
||||
|
||||
# Fedora
|
||||
sudo dnf install -y podman podman-compose
|
||||
```
|
||||
|
||||
2. **Convert Docker Compose to Podman**:
|
||||
```bash
|
||||
# Podman Compose uses same syntax
|
||||
podman-compose up -d
|
||||
```
|
||||
|
||||
3. **Generate systemd service**:
|
||||
```bash
|
||||
# Generate service file for melodee container
|
||||
podman generate systemd --new --name melodee > ~/.config/systemd/user/melodee.service
|
||||
|
||||
# Enable service
|
||||
systemctl --user enable melodee.service
|
||||
systemctl --user start melodee.service
|
||||
```
|
||||
|
||||
**Rootless Considerations**:
|
||||
- **Port binding**: Ports <1024 require root or `sysctl net.ipv4.ip_unprivileged_port_start=80`
|
||||
- **Volume permissions**: Ensure user has read/write access to volume paths
|
||||
- **Resource limits**: Rootless containers have lower default limits
|
||||
|
||||
## Backup and Recovery
|
||||
|
||||
### Database Backup
|
||||
|
||||
**Automated Daily Backups**:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/backups/postgres"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/melodee_$TIMESTAMP.sql.gz"
|
||||
|
||||
# Create backup
|
||||
docker exec melodee-postgres pg_dump -U melodee melodee | gzip > $BACKUP_FILE
|
||||
|
||||
# Verify backup
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Backup successful: $BACKUP_FILE"
|
||||
else
|
||||
echo "Backup failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Retain last 30 days
|
||||
find $BACKUP_DIR -name "melodee_*.sql.gz" -mtime +30 -delete
|
||||
|
||||
# Upload to S3 (optional)
|
||||
aws s3 cp $BACKUP_FILE s3://melodee-backups/postgres/
|
||||
```
|
||||
|
||||
**Cron Schedule**:
|
||||
```cron
|
||||
0 2 * * * /usr/local/bin/backup-melodee.sh
|
||||
```
|
||||
|
||||
**Restore from Backup**:
|
||||
```bash
|
||||
# Stop Melodee
|
||||
docker-compose stop melodee
|
||||
|
||||
# Restore database
|
||||
gunzip -c /backups/postgres/melodee_20250428_020000.sql.gz | \
|
||||
docker exec -i melodee-postgres psql -U melodee melodee
|
||||
|
||||
# Start Melodee
|
||||
docker-compose start melodee
|
||||
```
|
||||
|
||||
### Volume Backup
|
||||
|
||||
**Backup Script**:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/backups/volumes"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Backup config volume (contains API keys)
|
||||
docker run --rm \
|
||||
-v melodee_config:/data \
|
||||
-v $BACKUP_DIR:/backup \
|
||||
alpine tar czf /backup/config_$TIMESTAMP.tar.gz -C /data .
|
||||
|
||||
# Backup data volume (MusicBrainz cache)
|
||||
docker run --rm \
|
||||
-v melodee_data:/data \
|
||||
-v $BACKUP_DIR:/backup \
|
||||
alpine tar czf /backup/data_$TIMESTAMP.tar.gz -C /data .
|
||||
|
||||
# Backup album-art volume
|
||||
docker run --rm \
|
||||
-v melodee_album-art:/data \
|
||||
-v $BACKUP_DIR:/backup \
|
||||
alpine tar czf /backup/album-art_$TIMESTAMP.tar.gz -C /data .
|
||||
```
|
||||
|
||||
**Restore Volumes**:
|
||||
```bash
|
||||
# Restore config volume
|
||||
docker run --rm \
|
||||
-v melodee_config:/data \
|
||||
-v $BACKUP_DIR:/backup \
|
||||
alpine tar xzf /backup/config_20250428_020000.tar.gz -C /data
|
||||
|
||||
# Restore data volume
|
||||
docker run --rm \
|
||||
-v melodee_data:/data \
|
||||
-v $BACKUP_DIR:/backup \
|
||||
alpine tar xzf /backup/data_20250428_020000.tar.gz -C /data
|
||||
```
|
||||
|
||||
### Disaster Recovery
|
||||
|
||||
**Full System Recovery**:
|
||||
|
||||
1. **Install Docker and Docker Compose** on new server
|
||||
2. **Restore docker-compose.yml and .env** files
|
||||
3. **Create volumes**:
|
||||
```bash
|
||||
docker volume create melodee_config
|
||||
docker volume create melodee_data
|
||||
docker volume create melodee_album-art
|
||||
docker volume create melodee_postgres-data
|
||||
```
|
||||
|
||||
4. **Restore volume data** from backups
|
||||
5. **Restore PostgreSQL database** from backup
|
||||
6. **Start services**:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
7. **Verify health**:
|
||||
```bash
|
||||
docker-compose ps
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
**Recovery Time Objective (RTO)**: 1-2 hours
|
||||
**Recovery Point Objective (RPO)**: 24 hours (daily backups)
|
||||
|
||||
## Monitoring and Logging
|
||||
|
||||
### Prometheus Metrics
|
||||
|
||||
**Metrics Endpoint**:
|
||||
```csharp
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapMetrics("/metrics");
|
||||
});
|
||||
```
|
||||
|
||||
**Prometheus Configuration**:
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'melodee'
|
||||
static_configs:
|
||||
- targets: ['melodee:5000']
|
||||
metrics_path: '/metrics'
|
||||
scrape_interval: 15s
|
||||
```
|
||||
|
||||
**Key Metrics**:
|
||||
- `http_requests_total`: Total HTTP requests
|
||||
- `http_request_duration_seconds`: Request latency
|
||||
- `dotnet_gc_collections_total`: Garbage collection count
|
||||
- `process_cpu_seconds_total`: CPU usage
|
||||
- `process_resident_memory_bytes`: Memory usage
|
||||
- `melodee_scrobbles_total`: Total scrobbles submitted
|
||||
- `melodee_library_tracks_total`: Total tracks in library
|
||||
|
||||
### Grafana Dashboard
|
||||
|
||||
**Dashboard Panels**:
|
||||
1. **Request Rate**: Requests per second
|
||||
2. **Response Time**: P50, P95, P99 latencies
|
||||
3. **Error Rate**: 4xx and 5xx responses
|
||||
4. **CPU Usage**: Process CPU percentage
|
||||
5. **Memory Usage**: Resident memory
|
||||
6. **Database Connections**: Active connections
|
||||
7. **Scrobble Rate**: Scrobbles per hour
|
||||
8. **Library Size**: Total tracks, albums, artists
|
||||
|
||||
### Log Aggregation
|
||||
|
||||
**Serilog to Elasticsearch**:
|
||||
```csharp
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://elasticsearch:9200"))
|
||||
{
|
||||
AutoRegisterTemplate = true,
|
||||
IndexFormat = "melodee-logs-{0:yyyy.MM.dd}"
|
||||
})
|
||||
.CreateLogger();
|
||||
```
|
||||
|
||||
**Kibana Queries**:
|
||||
```
|
||||
# Errors in last hour
|
||||
level:Error AND @timestamp:[now-1h TO now]
|
||||
|
||||
# Slow requests (>1s)
|
||||
http.request.duration:>1000
|
||||
|
||||
# Failed scrobbles
|
||||
message:"scrobble failed"
|
||||
```
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### HTTPS Configuration
|
||||
|
||||
**Let's Encrypt with Certbot**:
|
||||
```bash
|
||||
# Install Certbot
|
||||
sudo apt-get install -y certbot
|
||||
|
||||
# Obtain certificate
|
||||
sudo certbot certonly --standalone -d music.example.com
|
||||
|
||||
# Configure Nginx with certificate (see Reverse Proxy section)
|
||||
```
|
||||
|
||||
**Certificate Renewal**:
|
||||
```cron
|
||||
0 0 1 * * certbot renew --quiet && systemctl reload nginx
|
||||
```
|
||||
|
||||
### Firewall Configuration
|
||||
|
||||
**UFW (Ubuntu)**:
|
||||
```bash
|
||||
# Allow SSH
|
||||
sudo ufw allow 22/tcp
|
||||
|
||||
# Allow HTTP/HTTPS (if using reverse proxy)
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
|
||||
# Allow Melodee (if direct access)
|
||||
sudo ufw allow 5000/tcp
|
||||
|
||||
# Enable firewall
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
### Secret Management
|
||||
|
||||
**Docker Secrets** (Swarm mode):
|
||||
```yaml
|
||||
services:
|
||||
melodee:
|
||||
secrets:
|
||||
- postgres_password
|
||||
- spotify_client_secret
|
||||
environment:
|
||||
- ConnectionStrings__DefaultConnection=Host=postgres;Database=melodee;Username=melodee;Password_FILE=/run/secrets/postgres_password
|
||||
|
||||
secrets:
|
||||
postgres_password:
|
||||
file: ./secrets/postgres_password.txt
|
||||
spotify_client_secret:
|
||||
file: ./secrets/spotify_client_secret.txt
|
||||
```
|
||||
|
||||
**Vault Integration**:
|
||||
```csharp
|
||||
var vaultClient = new VaultClient(new VaultClientSettings("http://vault:8200", "vault-token"));
|
||||
var secret = await vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync("melodee/postgres");
|
||||
var password = secret.Data.Data["password"].ToString();
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### PostgreSQL Optimization
|
||||
|
||||
```sql
|
||||
-- Increase shared buffers (25% of RAM)
|
||||
ALTER SYSTEM SET shared_buffers = '2GB';
|
||||
|
||||
-- Increase work memory for complex queries
|
||||
ALTER SYSTEM SET work_mem = '64MB';
|
||||
|
||||
-- Increase maintenance work memory for VACUUM
|
||||
ALTER SYSTEM SET maintenance_work_mem = '512MB';
|
||||
|
||||
-- Optimize for SSD
|
||||
ALTER SYSTEM SET random_page_cost = 1.1;
|
||||
|
||||
-- Enable query planning statistics
|
||||
ALTER SYSTEM SET track_activity_query_size = 2048;
|
||||
|
||||
-- Reload configuration
|
||||
SELECT pg_reload_conf();
|
||||
```
|
||||
|
||||
### .NET Runtime Optimization
|
||||
|
||||
**Environment Variables**:
|
||||
```yaml
|
||||
environment:
|
||||
- DOTNET_GCServer=1 # Server GC mode
|
||||
- DOTNET_GCConcurrent=1 # Concurrent GC
|
||||
- DOTNET_GCRetainVM=1 # Retain virtual memory
|
||||
- DOTNET_ThreadPool_MinThreads=50 # Minimum thread pool size
|
||||
- DOTNET_ThreadPool_MaxThreads=500 # Maximum thread pool size
|
||||
```
|
||||
|
||||
### Caching Configuration
|
||||
|
||||
**Redis Cache**:
|
||||
```yaml
|
||||
services:
|
||||
redis:
|
||||
image: redis:7
|
||||
command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
```
|
||||
|
||||
**Application Configuration**:
|
||||
```csharp
|
||||
services.AddStackExchangeRedisCache(options =>
|
||||
{
|
||||
options.Configuration = "redis:6379";
|
||||
options.InstanceName = "melodee:";
|
||||
});
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Melodee's deployment architecture demonstrates production-ready containerization with Docker multi-stage builds, automatic migrations, and comprehensive volume management. The 12 persistent volumes ensure data persistence, while health checks and logging enable robust monitoring.
|
||||
|
||||
Key strengths:
|
||||
- **Easy deployment**: Single-command Docker Compose setup
|
||||
- **Automatic migrations**: Database schema updates on startup
|
||||
- **Raspberry Pi support**: ARM64 compatibility for low-power deployments
|
||||
- **Podman compatibility**: Rootless container runtime support
|
||||
|
||||
Key challenges:
|
||||
- **Horizontal scaling**: Blazor Server requires sticky sessions and Redis backplane
|
||||
- **Backup complexity**: 12 volumes require coordinated backup strategy
|
||||
- **Secret management**: API keys in environment variables (consider Vault)
|
||||
|
||||
The architecture positions Melodee for both simple self-hosted deployments and advanced production configurations with high availability and monitoring.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,377 @@
|
||||
# Melodee: Project Overview
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Melodee is a self-hosted music server and metadata aggregator built on .NET 10 and Blazor Server. The project positions itself as a modern alternative to traditional music servers, emphasizing metadata quality, multi-protocol API support, and extensibility. With 62 GitHub stars and active development, Melodee represents a niche but technically sophisticated approach to personal music library management.
|
||||
|
||||
The system's core value proposition centers on intelligent metadata aggregation from six different providers, a multi-stage processing pipeline that transforms raw audio files into organized library entries, and compatibility with existing music client ecosystems through three distinct API protocols.
|
||||
|
||||
## Project Identity
|
||||
|
||||
**Repository**: https://github.com/melodee-project/melodee
|
||||
**Version**: 1.8.0
|
||||
**License**: MIT
|
||||
**Primary Language**: C# (.NET 10)
|
||||
**UI Framework**: Blazor Server with Radzen components
|
||||
**Database**: PostgreSQL 17 (primary), SQLite (MusicBrainz cache)
|
||||
**Stars**: 62
|
||||
**Status**: Active development
|
||||
|
||||
The MIT license makes Melodee suitable for both personal and commercial use without significant legal constraints. The choice of .NET 10 indicates commitment to modern framework features and performance characteristics, though it also creates a dependency on Microsoft's release cycle.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### Multi-Protocol API Support
|
||||
|
||||
Melodee implements three distinct API protocols, each serving different client ecosystems:
|
||||
|
||||
1. **Native REST API** (`/api/v1/`): JWT-based authentication, modern RESTful design, full feature access
|
||||
2. **OpenSubsonic** (`/rest/`): Token and salt authentication, compatibility with Subsonic clients (DSub, Ultrasonic, Sublime Music)
|
||||
3. **Jellyfin API** (`/api/jf/`): Custom token authentication, compatibility with Jellyfin clients
|
||||
|
||||
This multi-protocol approach maximizes client compatibility without forcing users into a single ecosystem. The rate limiting differs per protocol: Native API allows 30 requests per 30 seconds, authentication endpoints limit to 10 per 60 seconds, and Jellyfin endpoints permit 200 per 60 seconds.
|
||||
|
||||
### Metadata Aggregation Pipeline
|
||||
|
||||
The system processes music files through four distinct stages:
|
||||
|
||||
1. **Inbound**: Raw file ingestion and validation
|
||||
2. **Staging**: Metadata extraction and provider queries
|
||||
3. **Storage**: File organization and normalization
|
||||
4. **Database**: Entity persistence and indexing
|
||||
|
||||
Six metadata providers contribute to the aggregation process:
|
||||
|
||||
- **MusicBrainz**: Primary source with local SQLite cache, monthly updates
|
||||
- **Last.fm**: Social metadata, play counts, similar artists
|
||||
- **Spotify**: Album art, popularity metrics (client credentials flow)
|
||||
- **iTunes**: Commercial metadata, preview URLs
|
||||
- **Deezer**: European market metadata
|
||||
- **Brave Search**: Fallback web search for obscure releases
|
||||
|
||||
The MusicBrainz cache strategy deserves attention. Rather than querying the remote API for every lookup, Melodee maintains a local SQLite database updated monthly. This reduces latency and respects MusicBrainz rate limits while ensuring metadata freshness.
|
||||
|
||||
### Background Job Architecture
|
||||
|
||||
Melodee uses Quartz.NET to orchestrate 17 background jobs with dependency chaining. Jobs handle:
|
||||
|
||||
- Metadata provider synchronization
|
||||
- Library scanning and updates
|
||||
- Scrobble submission (Last.fm and internal)
|
||||
- Database maintenance and optimization
|
||||
- Cache invalidation
|
||||
- Statistics calculation
|
||||
- Podcast feed updates
|
||||
|
||||
Job chaining allows complex workflows. For example, a library scan job triggers metadata enrichment jobs, which then trigger cache invalidation, which finally triggers statistics recalculation. This declarative approach keeps the system responsive while handling computationally expensive operations asynchronously.
|
||||
|
||||
## Technical Foundation
|
||||
|
||||
### .NET 10 and Blazor Server
|
||||
|
||||
The choice of Blazor Server over Blazor WebAssembly or traditional SPA frameworks has specific implications:
|
||||
|
||||
**Advantages**:
|
||||
- Full .NET runtime access without WASM limitations
|
||||
- Smaller initial payload (no framework download)
|
||||
- Direct database access without API layer overhead
|
||||
- Real-time updates via SignalR (used for Party Mode)
|
||||
|
||||
**Tradeoffs**:
|
||||
- Server-side rendering requires persistent connection
|
||||
- Higher server resource usage per user
|
||||
- Network latency affects UI responsiveness
|
||||
- Scaling requires sticky sessions or Redis backplane
|
||||
|
||||
For a self-hosted music server with typically 1-10 concurrent users, these tradeoffs favor Blazor Server. The SignalR connection enables Party Mode, where multiple users see synchronized playback state.
|
||||
|
||||
### Database Architecture
|
||||
|
||||
PostgreSQL 17 serves as the primary data store with over 100 migrations and 40+ entities. The migration count suggests iterative development and schema evolution. Entity Framework Core 10 provides the ORM layer.
|
||||
|
||||
SQLite handles the MusicBrainz cache separately. This dual-database approach isolates read-heavy cache queries from transactional music library operations. The cache can be rebuilt without affecting user data.
|
||||
|
||||
Key entity categories:
|
||||
- **Library entities**: Albums, Artists, Tracks, Genres
|
||||
- **User entities**: Users, Playlists, Favorites, Scrobbles
|
||||
- **Metadata entities**: Provider mappings, external IDs, cached responses
|
||||
- **System entities**: Jobs, Logs, Settings, Health checks
|
||||
|
||||
The 100+ migrations indicate active schema development. This can complicate upgrades if migrations aren't carefully managed, but the Docker entrypoint.sh script handles automatic migration application on container startup.
|
||||
|
||||
### Audio Processing
|
||||
|
||||
FFmpeg handles transcoding for format conversion and bitrate adjustment. ImageSharp processes album art (resizing, format conversion, optimization). Audio tagging uses two libraries:
|
||||
|
||||
- **ATL (Audio Tools Library)**: Primary tagging engine, supports 20+ formats
|
||||
- **IdSharp**: Fallback for ID3v2 edge cases
|
||||
|
||||
This dual-library approach suggests the developers encountered limitations in a single tagging library and opted for redundancy rather than forking or extensive patching.
|
||||
|
||||
## User-Facing Features
|
||||
|
||||
### Party Mode
|
||||
|
||||
SignalR-powered synchronized playback across multiple clients. One user controls playback, others see real-time updates. This feature differentiates Melodee from traditional music servers that treat each session independently.
|
||||
|
||||
Implementation likely uses SignalR groups to broadcast playback state changes. The Blazor Server architecture makes this natural since the SignalR connection already exists for UI updates.
|
||||
|
||||
### Podcast Support
|
||||
|
||||
Melodee handles podcast feeds alongside music libraries. This positions it as a unified media server rather than music-only. Podcast-specific features likely include:
|
||||
|
||||
- RSS feed parsing and updates
|
||||
- Episode download management
|
||||
- Playback position tracking
|
||||
- Subscription management
|
||||
|
||||
The background job system handles periodic feed checks and episode downloads.
|
||||
|
||||
### MQL Query Language
|
||||
|
||||
Melodee implements a custom query language (MQL) for advanced library searches. This suggests power users can construct complex queries beyond simple text search. Examples might include:
|
||||
|
||||
- `artist:Radiohead AND year:>2000`
|
||||
- `genre:Jazz OR genre:Blues`
|
||||
- `playcount:>10 AND rating:>=4`
|
||||
|
||||
The implementation likely uses a parser (possibly ANTLR or hand-written recursive descent) to convert MQL strings into LINQ expressions or SQL queries.
|
||||
|
||||
### Charts and Analytics
|
||||
|
||||
The system generates charts based on listening history:
|
||||
|
||||
- Most played tracks/albums/artists
|
||||
- Listening trends over time
|
||||
- Genre distribution
|
||||
- Discovery metrics (new vs. familiar content)
|
||||
|
||||
These features require the scrobbling system to capture play events and the background jobs to aggregate statistics.
|
||||
|
||||
### User Requests
|
||||
|
||||
Users can request missing albums or corrections. This creates a feedback loop where library gaps become visible to administrators. The feature likely stores requests as database entities with status tracking (pending, fulfilled, rejected).
|
||||
|
||||
## Internationalization
|
||||
|
||||
Support for 10 languages indicates a global user base or internationalization-first design. Blazor's localization system uses resource files (.resx) for string management. The 10 languages suggest community contributions for translations.
|
||||
|
||||
Language support affects:
|
||||
- UI strings
|
||||
- Error messages
|
||||
- Email templates
|
||||
- API documentation
|
||||
|
||||
The Scalar API documentation tool likely generates localized API docs automatically.
|
||||
|
||||
## Authentication and Security
|
||||
|
||||
### Google OAuth Integration
|
||||
|
||||
OAuth support allows users to authenticate with Google accounts rather than managing separate credentials. This reduces friction for new users and delegates security concerns to Google's infrastructure.
|
||||
|
||||
Implementation uses standard OAuth 2.0 authorization code flow:
|
||||
1. User clicks "Sign in with Google"
|
||||
2. Redirect to Google consent screen
|
||||
3. Google redirects back with authorization code
|
||||
4. Melodee exchanges code for access token
|
||||
5. Melodee retrieves user profile
|
||||
6. Melodee creates or updates local user account
|
||||
|
||||
### JWT for Native API
|
||||
|
||||
The native REST API uses JWT tokens for stateless authentication. Clients receive a token after login and include it in the `Authorization: Bearer <token>` header for subsequent requests.
|
||||
|
||||
JWT advantages:
|
||||
- Stateless (no server-side session storage)
|
||||
- Self-contained (claims embedded in token)
|
||||
- Scalable (no session affinity required)
|
||||
|
||||
JWT tradeoffs:
|
||||
- Token revocation requires additional infrastructure (blacklist or short expiry)
|
||||
- Token size larger than session IDs
|
||||
- Clock skew can cause validation issues
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Per-protocol rate limits prevent abuse:
|
||||
- **API endpoints**: 30 requests per 30 seconds
|
||||
- **Authentication**: 10 requests per 60 seconds
|
||||
- **Jellyfin endpoints**: 200 requests per 60 seconds
|
||||
|
||||
The higher Jellyfin limit suggests those clients make more frequent requests, possibly for real-time playback state updates.
|
||||
|
||||
Rate limiting implementation likely uses in-memory sliding window counters keyed by IP address or user ID. For distributed deployments, this would require Redis or similar shared state.
|
||||
|
||||
## Observability
|
||||
|
||||
### Logging with Serilog
|
||||
|
||||
Serilog provides structured logging with two sinks:
|
||||
- **Console**: Human-readable output for development and container logs
|
||||
- **File (CLEF)**: Compact Log Event Format for machine parsing
|
||||
|
||||
CLEF (Compact Log Event Format) is JSON-based, making logs easily ingestible by log aggregation tools (Seq, Elasticsearch, Splunk). This suggests the developers anticipate production deployments where centralized logging matters.
|
||||
|
||||
### Health Checks
|
||||
|
||||
The `/health` endpoint exposes system status for monitoring tools. Health checks likely verify:
|
||||
- Database connectivity
|
||||
- Metadata provider availability
|
||||
- Background job status
|
||||
- Disk space
|
||||
- Cache validity
|
||||
|
||||
Kubernetes and Docker Swarm can use this endpoint for liveness and readiness probes.
|
||||
|
||||
### Admin UI
|
||||
|
||||
Blazor-based admin interface provides visibility into:
|
||||
- Job execution history and status
|
||||
- User management
|
||||
- Library statistics
|
||||
- System settings
|
||||
- Log viewing
|
||||
|
||||
This eliminates the need for database access or log file inspection for routine administration.
|
||||
|
||||
## Platform Compatibility
|
||||
|
||||
### Raspberry Pi Support
|
||||
|
||||
Explicit Raspberry Pi compatibility indicates ARM architecture support and resource-conscious design. Running on Raspberry Pi 4 (4GB RAM) requires:
|
||||
- Efficient memory usage
|
||||
- ARM64 .NET runtime
|
||||
- Minimal CPU overhead for background jobs
|
||||
- Optimized database queries
|
||||
|
||||
This positions Melodee as suitable for home server deployments on low-power hardware.
|
||||
|
||||
### Podman Support
|
||||
|
||||
Podman compatibility alongside Docker shows awareness of rootless container runtimes. Podman's daemonless architecture and rootless mode appeal to security-conscious users.
|
||||
|
||||
The Docker Compose file likely works with Podman Compose with minimal or no modifications. Volume mounts and networking must avoid Docker-specific assumptions.
|
||||
|
||||
## Development Practices
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
Three testing frameworks indicate comprehensive test coverage:
|
||||
|
||||
1. **xUnit**: Unit and integration tests for business logic
|
||||
2. **bUnit**: Blazor component testing
|
||||
3. **NBomber**: Load and performance testing
|
||||
|
||||
The inclusion of NBomber suggests performance is a first-class concern. Load tests likely verify:
|
||||
- API throughput under concurrent requests
|
||||
- Database query performance with large libraries
|
||||
- Memory usage during metadata aggregation
|
||||
- Background job execution time
|
||||
|
||||
### Code Quality
|
||||
|
||||
Biome linting enforces code style and catches common errors. Biome is a fast, Rust-based linter and formatter that supports JavaScript, TypeScript, JSON, and CSS. Its presence suggests frontend code (likely for admin UI customization or build scripts) follows consistent style rules.
|
||||
|
||||
The combination of .NET analyzers (built into SDK) and Biome creates a multi-layered quality gate.
|
||||
|
||||
## Competitive Positioning
|
||||
|
||||
Melodee competes with established music servers:
|
||||
|
||||
- **Subsonic/Airsonic**: Older Java-based servers with large client ecosystems
|
||||
- **Navidrome**: Go-based, lightweight, OpenSubsonic-compatible
|
||||
- **Jellyfin**: Full media server (music, video, TV) with broad client support
|
||||
- **Plex**: Commercial media server with free tier
|
||||
- **Emby**: Commercial media server, Jellyfin's predecessor
|
||||
|
||||
Melodee's differentiators:
|
||||
- **Metadata quality**: Six providers vs. typical 1-2
|
||||
- **Multi-protocol**: Native + OpenSubsonic + Jellyfin vs. single protocol
|
||||
- **Modern stack**: .NET 10 + Blazor vs. older frameworks
|
||||
- **Party Mode**: Synchronized playback vs. independent sessions
|
||||
- **MQL**: Advanced queries vs. basic search
|
||||
|
||||
The 62 stars suggest Melodee hasn't achieved mainstream adoption. This could reflect:
|
||||
- Newer project (less time to accumulate stars)
|
||||
- Niche appeal (power users who value metadata quality)
|
||||
- Competition from established alternatives
|
||||
- .NET ecosystem smaller than Go/Rust for self-hosted tools
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Personal Music Library
|
||||
|
||||
Primary use case: individual managing a local music collection with high metadata standards. The six-provider aggregation ensures accurate artist names, release dates, genres, and album art even for obscure releases.
|
||||
|
||||
### Family Media Server
|
||||
|
||||
Multiple user accounts, playlists, and Party Mode support family sharing. Google OAuth simplifies account creation for non-technical family members.
|
||||
|
||||
### Podcast Aggregator
|
||||
|
||||
Podcast support makes Melodee a unified audio server. Users avoid separate podcast apps and music apps.
|
||||
|
||||
### Music Discovery Platform
|
||||
|
||||
Charts, analytics, and Last.fm integration enable discovery workflows. Users see listening patterns and explore similar artists.
|
||||
|
||||
### Development Platform
|
||||
|
||||
MIT license and modern .NET stack make Melodee suitable as a foundation for custom music server projects. Developers can fork and extend without licensing concerns.
|
||||
|
||||
## Limitations and Considerations
|
||||
|
||||
### Blazor Server Scalability
|
||||
|
||||
Persistent SignalR connections limit horizontal scaling. Each user consumes server memory and CPU for UI rendering. Scaling beyond 50-100 concurrent users requires careful architecture (Redis backplane, sticky sessions, or migration to Blazor WebAssembly).
|
||||
|
||||
### Metadata Provider Dependencies
|
||||
|
||||
Six providers create six points of failure. If MusicBrainz, Last.fm, or Spotify change APIs or rate limits, metadata quality degrades. The local MusicBrainz cache mitigates this for the primary provider.
|
||||
|
||||
### Migration Complexity
|
||||
|
||||
100+ migrations complicate upgrades, especially if users skip versions. The Docker entrypoint handles automatic migration, but rollback scenarios require careful planning.
|
||||
|
||||
### .NET Ecosystem
|
||||
|
||||
.NET 10 requires users comfortable with .NET runtime installation or Docker. This narrows the audience compared to Go or Rust single-binary distributions.
|
||||
|
||||
### Client Compatibility
|
||||
|
||||
While OpenSubsonic and Jellyfin APIs provide broad client support, the native API requires custom clients or API consumers. The project's 62 stars suggest limited native client development.
|
||||
|
||||
## Future Potential
|
||||
|
||||
### Federated Libraries
|
||||
|
||||
Multiple Melodee instances could federate, allowing users to share libraries across households while maintaining local control.
|
||||
|
||||
### Machine Learning
|
||||
|
||||
Listening history and metadata enable recommendation engines, auto-playlist generation, and mood-based categorization.
|
||||
|
||||
### Blockchain Integration
|
||||
|
||||
NFT-based music ownership or decentralized metadata storage could differentiate Melodee in web3 contexts.
|
||||
|
||||
### Mobile Apps
|
||||
|
||||
Native iOS and Android apps using the REST API would reduce dependence on third-party clients.
|
||||
|
||||
### Video Support
|
||||
|
||||
Expanding beyond audio to music videos or concerts would position Melodee as a full media server competitor to Jellyfin and Plex.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Melodee represents a technically sophisticated music server built on modern .NET foundations. The multi-protocol API support, six-provider metadata aggregation, and Blazor Server UI create a compelling package for users who prioritize metadata quality and extensibility.
|
||||
|
||||
The project's 62 stars indicate niche appeal rather than mainstream adoption. This likely reflects the competitive landscape (established alternatives like Navidrome and Jellyfin) and the .NET ecosystem's smaller footprint in self-hosted software compared to Go or Rust.
|
||||
|
||||
For developers evaluating music server options, Melodee offers:
|
||||
- **Strengths**: Metadata quality, modern stack, multi-protocol support, MIT license
|
||||
- **Tradeoffs**: Blazor Server scalability, .NET runtime dependency, smaller community
|
||||
|
||||
The project's active development (version 1.8.0, 100+ migrations) suggests ongoing improvement. Whether Melodee achieves broader adoption depends on community growth, client ecosystem development, and continued differentiation from established competitors.
|
||||
Reference in New Issue
Block a user