Updates deployment configuration: - Adds strict systemd sandboxing (ProtectSystem, DynamicUser, etc) - Pins flake input to nixos-24.11 for stability - Updates docs to reflect hardening
338 lines
11 KiB
Markdown
338 lines
11 KiB
Markdown
# MusicLink Bot - Platform Setup Guide
|
|
|
|
## Overview
|
|
|
|
MusicLink is a bot that detects music links (Spotify, YouTube, Apple Music, etc.) in chat messages and responds with equivalent links on other streaming services.
|
|
|
|
It uses the [idonthavespotify](https://github.com/sjdonado/idonthavespotify) API for link conversion, so **no Spotify/YouTube API credentials are required**.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────── jrz1 (NixOS VPS) ───────────────────────────┐
|
|
│ │
|
|
│ ┌─────────────────────────┐ ┌─────────────────────────────┐ │
|
|
│ │ matterbridge.service │ │ musiclink.service │ │
|
|
│ │ │ │ │ │
|
|
│ │ Bridges chat platforms │ WS │ Detects music links and │ │
|
|
│ │ and exposes local API │◄─────►│ responds with alternatives │ │
|
|
│ │ │ │ │ │
|
|
│ │ - Slack bridge │ │ - Link detection (regex) │ │
|
|
│ │ - API bridge (:4242) │ │ - Calls idonthavespotify │ │
|
|
│ │ - (Discord, Matrix │ │ API for conversion │ │
|
|
│ │ can be added later) │ │ │ │
|
|
│ └────────────┬────────────┘ └──────────────┬──────────────┘ │
|
|
│ │ │ │
|
|
└───────────────┼────────────────────────────────────┼────────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌───────────┐ ┌────────────────────┐
|
|
│ Slack │ │ idonthavespotify │
|
|
└───────────┘ │ (external API) │
|
|
└────────────────────┘
|
|
```
|
|
|
|
### How It Works
|
|
|
|
1. User posts a music link in Slack (e.g., `https://open.spotify.com/track/abc123`)
|
|
2. Matterbridge forwards the message to musiclink via WebSocket
|
|
3. MusicLink detects the music URL and calls the idonthavespotify API
|
|
4. The API returns equivalent links for Spotify, YouTube, Apple Music, Tidal, Deezer, etc.
|
|
5. MusicLink formats and sends the response back through matterbridge to Slack
|
|
|
|
### Why Matterbridge?
|
|
|
|
Matterbridge acts as a universal adapter. Instead of writing platform-specific code for each chat service, we:
|
|
|
|
1. Run matterbridge (connects to Slack, Discord, Matrix, etc.)
|
|
2. Matterbridge exposes a simple WebSocket API locally
|
|
3. Our bot connects to that API and receives/sends messages as JSON
|
|
|
|
This means:
|
|
- **Adding a new platform** = config change in matterbridge (no code changes)
|
|
- **Bot code stays simple** = just WebSocket + JSON, works from any language
|
|
- **Already in nixpkgs** = `services.matterbridge` module ready to use
|
|
|
|
### Why idonthavespotify API?
|
|
|
|
- **No API credentials needed** - No Spotify Developer account, no YouTube API keys
|
|
- **Supports 8 platforms** - Spotify, YouTube, Apple Music, Deezer, Tidal, SoundCloud, Qobuz, Bandcamp
|
|
- **Simple integration** - Single HTTP POST, returns all platform links
|
|
- **Open source** - Can self-host if needed later
|
|
|
|
## Components
|
|
|
|
### 1. Matterbridge (existing nixpkgs module)
|
|
|
|
**Package:** `pkgs.matterbridge` (v1.26.0)
|
|
**Module:** `services.matterbridge`
|
|
**Docs:** https://github.com/42wim/matterbridge/wiki
|
|
|
|
### 2. MusicLink Bot (this repo)
|
|
|
|
**Language:** Go
|
|
**Location:** `/home/dan/proj/musiclink`
|
|
**Connects to:** Matterbridge API via WebSocket
|
|
**Uses:** idonthavespotify API for link conversion
|
|
|
|
## NixOS Configuration
|
|
|
|
### Matterbridge Service
|
|
|
|
```nix
|
|
{ config, pkgs, ... }:
|
|
|
|
{
|
|
services.matterbridge = {
|
|
enable = true;
|
|
configPath = "/var/lib/matterbridge/matterbridge.toml";
|
|
};
|
|
|
|
# Ensure config directory exists
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/matterbridge 0750 matterbridge matterbridge -"
|
|
];
|
|
}
|
|
```
|
|
|
|
### MusicLink Service
|
|
|
|
```nix
|
|
{ config, pkgs, ... }:
|
|
|
|
let
|
|
musiclink = pkgs.buildGoModule {
|
|
pname = "musiclink";
|
|
version = "0.1.0";
|
|
src = /home/dan/proj/musiclink; # or fetchFromGitHub
|
|
vendorHash = null; # update after first build
|
|
};
|
|
in
|
|
{
|
|
systemd.services.musiclink = {
|
|
description = "MusicLink Bot";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" "matterbridge.service" ];
|
|
requires = [ "matterbridge.service" ];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
ExecStart = "${musiclink}/bin/musiclink -config /var/lib/musiclink/config.toml";
|
|
Restart = "always";
|
|
RestartSec = "5s";
|
|
|
|
# Hardening
|
|
DynamicUser = true;
|
|
StateDirectory = "musiclink";
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
NoNewPrivileges = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
RestrictNamespaces = true;
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
PrivateMounts = true;
|
|
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
|
|
};
|
|
};
|
|
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/musiclink 0750 musiclink musiclink -"
|
|
];
|
|
}
|
|
```
|
|
|
|
## Configuration Files
|
|
|
|
### Matterbridge (`/var/lib/matterbridge/matterbridge.toml`)
|
|
|
|
```toml
|
|
# =============================================================================
|
|
# Matterbridge Configuration
|
|
# Bridges Slack to local API for musiclink bot
|
|
# =============================================================================
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Slack Connection
|
|
# -----------------------------------------------------------------------------
|
|
[slack.workspace]
|
|
# Bot User OAuth Token (starts with xoxb-)
|
|
# Get from: https://api.slack.com/apps → Your App → OAuth & Permissions
|
|
Token = "xoxb-YOUR-SLACK-BOT-TOKEN"
|
|
|
|
# Show username before messages
|
|
PrefixMessagesWithNick = true
|
|
|
|
# Optional: customize how remote users appear
|
|
RemoteNickFormat = "[{PROTOCOL}] {NICK}"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Local API Bridge (for musiclink bot)
|
|
# -----------------------------------------------------------------------------
|
|
[api.musiclink]
|
|
# Only bind to localhost (not exposed externally)
|
|
BindAddress = "127.0.0.1:4242"
|
|
|
|
# Shared secret for bot authentication
|
|
Token = "GENERATE-A-RANDOM-TOKEN-HERE"
|
|
|
|
# Message buffer size
|
|
Buffer = 1000
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Gateway Configuration
|
|
# Routes messages between bridges
|
|
# -----------------------------------------------------------------------------
|
|
[[gateway]]
|
|
name = "main"
|
|
enable = true
|
|
|
|
# Slack channels to bridge
|
|
[[gateway.inout]]
|
|
account = "slack.workspace"
|
|
channel = "music" # Change to your channel name
|
|
|
|
# API endpoint for musiclink bot
|
|
[[gateway.inout]]
|
|
account = "api.musiclink"
|
|
channel = "api"
|
|
```
|
|
|
|
### MusicLink (`/var/lib/musiclink/config.toml`)
|
|
|
|
```toml
|
|
# =============================================================================
|
|
# MusicLink Bot Configuration
|
|
#
|
|
# Uses idonthavespotify API - no external API credentials needed!
|
|
# =============================================================================
|
|
|
|
[matterbridge]
|
|
# Must match matterbridge API bridge config
|
|
url = "ws://127.0.0.1:4242/api/websocket"
|
|
token = "GENERATE-A-RANDOM-TOKEN-HERE" # Same as matterbridge [api.musiclink].Token
|
|
gateway = "main"
|
|
|
|
# Bot identity
|
|
username = "MusicLink"
|
|
avatar = "" # Optional URL
|
|
```
|
|
|
|
That's the entire config - no Spotify/YouTube API keys required!
|
|
|
|
## Secrets Management
|
|
|
|
The only secrets needed are:
|
|
- **Slack bot token** (`xoxb-...`)
|
|
- **Matterbridge↔MusicLink shared token** (generate a random string)
|
|
|
|
Options for managing secrets:
|
|
|
|
1. **sops-nix** - Encrypted secrets in repo, decrypted at deploy time
|
|
2. **agenix** - Age-encrypted secrets
|
|
3. **Environment files** - `EnvironmentFile=` in systemd service
|
|
|
|
Example with environment file:
|
|
|
|
```nix
|
|
systemd.services.matterbridge.serviceConfig = {
|
|
EnvironmentFile = "/run/secrets/matterbridge.env";
|
|
};
|
|
```
|
|
|
|
```bash
|
|
# /run/secrets/matterbridge.env
|
|
MATTERBRIDGE_SLACK_TOKEN=xoxb-...
|
|
```
|
|
|
|
Then in matterbridge.toml:
|
|
```toml
|
|
[slack.workspace]
|
|
Token = "${MATTERBRIDGE_SLACK_TOKEN}"
|
|
```
|
|
|
|
## Slack App Setup
|
|
|
|
1. Go to https://api.slack.com/apps
|
|
2. Create New App → From scratch
|
|
3. App name: "MusicLink", select workspace
|
|
4. **OAuth & Permissions:**
|
|
- Bot Token Scopes needed:
|
|
- `channels:history` - Read messages
|
|
- `channels:read` - See channel list
|
|
- `chat:write` - Send messages
|
|
- `users:read` - Get user info
|
|
5. **Install to Workspace**
|
|
6. Copy **Bot User OAuth Token** (`xoxb-...`)
|
|
7. Invite bot to channel: `/invite @MusicLink`
|
|
|
|
## Deployment Checklist
|
|
|
|
- [ ] Slack app created and bot token obtained
|
|
- [ ] Generate random token for matterbridge↔musiclink auth
|
|
- [ ] Add matterbridge NixOS config
|
|
- [ ] Create `/var/lib/matterbridge/matterbridge.toml`
|
|
- [ ] Add musiclink NixOS config
|
|
- [ ] Create `/var/lib/musiclink/config.toml`
|
|
- [ ] Deploy NixOS config (`nixos-rebuild switch`)
|
|
- [ ] Verify services: `systemctl status matterbridge musiclink`
|
|
- [ ] Test: Post a Spotify link in the configured Slack channel
|
|
|
|
## Logs & Debugging
|
|
|
|
```bash
|
|
# Check service status
|
|
systemctl status matterbridge
|
|
systemctl status musiclink
|
|
|
|
# View logs
|
|
journalctl -u matterbridge -f
|
|
journalctl -u musiclink -f
|
|
|
|
# Test matterbridge API directly
|
|
curl -H "Authorization: Bearer YOUR-TOKEN" http://localhost:4242/api/health
|
|
|
|
# Test idonthavespotify API directly
|
|
curl -X POST 'https://idonthavespotify.sjdonado.com/api/search?v=1' \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"link":"https://open.spotify.com/track/4iV5W9uYEdYUVa79Axb7Rh"}'
|
|
```
|
|
|
|
## Adding More Platforms Later
|
|
|
|
To add Discord, Matrix, or other platforms, just update `matterbridge.toml`:
|
|
|
|
```toml
|
|
# Add Discord
|
|
[discord.myserver]
|
|
Token = "discord-bot-token"
|
|
Server = "server-id"
|
|
|
|
# Add to gateway
|
|
[[gateway.inout]]
|
|
account = "discord.myserver"
|
|
channel = "music"
|
|
```
|
|
|
|
No changes needed to musiclink bot - matterbridge handles the bridging.
|
|
|
|
## Self-Hosting idonthavespotify (Optional)
|
|
|
|
If you want to avoid the external API dependency, idonthavespotify can be self-hosted:
|
|
|
|
- Repo: https://github.com/sjdonado/idonthavespotify
|
|
- Requires: Docker or Bun runtime
|
|
- Note: Self-hosting still requires Spotify/Tidal API credentials
|
|
|
|
For most use cases, the hosted API at `idonthavespotify.sjdonado.com` is sufficient.
|
|
|
|
## Questions?
|
|
|
|
- Matterbridge docs: https://github.com/42wim/matterbridge/wiki
|
|
- idonthavespotify: https://github.com/sjdonado/idonthavespotify
|
|
- MusicLink repo: `/home/dan/proj/musiclink`
|