feat: complete musiclink bot integration with verified VM checks

This commit is contained in:
Dan 2026-01-20 13:40:47 -08:00
parent 82fce7f4e4
commit 4adf6723c5
12 changed files with 196 additions and 8 deletions

View file

@ -1 +1 @@
0.44.0
0.47.1

View file

@ -1 +1 @@
ops-jrz1-6ip
ops-jrz1-k3e0

View file

@ -1 +1 @@
/nix/store/c1qr5g1z0n91x5qw9cpc2wyk66hsavkh-ai-skill-orch
/nix/store/5s89k0jw8xn415n29li5qd2z9wm1zy6r-ai-skill-orch

View file

@ -1 +1 @@
/nix/store/pnw3i2sn988jv6dwjps7ilvlniz76pbn-ai-skill-worklog
/nix/store/xg0z64szmizn99yw8psrd9ffsiy2mfxi-ai-skill-worklog

1
.gemini/skills/orch Symbolic link
View file

@ -0,0 +1 @@
/nix/store/5s89k0jw8xn415n29li5qd2z9wm1zy6r-ai-skill-orch

1
.gemini/skills/worklog Symbolic link
View file

@ -0,0 +1 @@
/nix/store/xg0z64szmizn99yw8psrd9ffsiy2mfxi-ai-skill-worklog

View file

@ -19,6 +19,11 @@
url = "github:steveyegge/beads";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
musiclink = {
url = "git+file:///home/dan/proj/musiclink";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, nixpkgs-unstable, sops-nix, ... }@inputs:
@ -34,6 +39,7 @@
};
opencode = inputs.opencode.packages.${system}.default;
beads = inputs.beads.packages.${system}.default;
musiclink = inputs.musiclink.packages.${system}.default;
in {
# Pre-deploy checks: nix flake check
checks.${system} = {
@ -55,7 +61,7 @@
# VM integration test - boots VM and verifies services
vm-integration = import ./tests/vm-integration.nix {
inherit pkgs pkgs-unstable opencode;
inherit pkgs pkgs-unstable opencode musiclink;
};
};
@ -75,6 +81,7 @@
};
opencode = inputs.opencode.packages.x86_64-linux.default;
beads = inputs.beads.packages.x86_64-linux.default;
musiclink = inputs.musiclink.packages.x86_64-linux.default;
};
modules = [
./configuration.nix
@ -98,6 +105,7 @@
};
opencode = inputs.opencode.packages.x86_64-linux.default;
beads = inputs.beads.packages.x86_64-linux.default;
musiclink = inputs.musiclink.packages.x86_64-linux.default;
};
modules = [
./configuration.nix

View file

@ -13,6 +13,7 @@
../modules/mautrix-whatsapp.nix
../modules/mautrix-gmessages.nix
../modules/maubot.nix
../modules/musiclink.nix
../modules/dev-services.nix
../modules/security/fail2ban.nix
../modules/security/ssh-hardening.nix

View file

@ -15,6 +15,7 @@
../modules/matrix-continuwuity.nix
../modules/mautrix-slack.nix
../modules/maubot.nix
../modules/musiclink.nix
../modules/dev-services.nix
../modules/security/fail2ban.nix
../modules/security/ssh-hardening.nix
@ -94,10 +95,18 @@
};
maubot = {
enable = true;
enable = false;
port = 29316;
plugins = [ ../modules/plugins/sna-instagram-bot.mbp ];
};
musiclink = {
enable = true;
matterbridge = {
enable = true;
slackChannel = "music";
};
};
};
# Local backup service (Phase 1: manual trigger)

View file

@ -95,6 +95,28 @@ in
description = "Maubot plugins to deploy";
};
};
musiclink = {
enable = mkOption {
type = types.bool;
default = false;
description = "Enable MusicLink bot";
};
matterbridge = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable bundled Matterbridge";
};
slackChannel = mkOption {
type = types.str;
default = "music";
description = "Slack channel for MusicLink";
};
};
};
};
config = mkIf cfg.enable {
@ -273,6 +295,15 @@ in
plugins = cfg.maubot.plugins;
};
# MusicLink Service
services.musiclink = mkIf cfg.musiclink.enable {
enable = true;
matterbridge = {
enable = cfg.musiclink.matterbridge.enable;
slackChannel = cfg.musiclink.matterbridge.slackChannel;
};
};
# Basic Nginx reverse proxy
services.nginx = {
enable = true;

136
modules/musiclink.nix Normal file
View file

@ -0,0 +1,136 @@
{ config, pkgs, lib, musiclink, ... }:
with lib;
let
cfg = config.services.musiclink;
musiclinkPkg = musiclink; # musiclink input passed via specialArgs is the package set? No, usually it's the flake itself if not mapped.
# But in flake.nix we did: musiclink = inputs.musiclink.packages.x86_64-linux.default;
# So `musiclink` here is the package.
in {
options.services.musiclink = {
enable = mkEnableOption "MusicLink bot with Matterbridge";
package = mkOption {
type = types.package;
default = musiclink;
description = "The MusicLink bot package";
};
matterbridge = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable bundled Matterbridge instance";
};
port = mkOption {
type = types.port;
default = 4242;
description = "Matterbridge API port";
};
slackChannel = mkOption {
type = types.str;
default = "music";
description = "Slack channel to bridge";
};
};
};
config = mkIf cfg.enable {
# -------------------------------------------------------------------------
# Matterbridge Service
# -------------------------------------------------------------------------
systemd.services.musiclink-matterbridge = mkIf cfg.matterbridge.enable {
description = "Matterbridge for MusicLink";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = "musiclink-matterbridge";
WorkingDirectory = "/var/lib/musiclink-matterbridge";
# Secrets
LoadCredential = [
"slack-bot-token:/run/secrets/slack-bot-token"
"slack-app-token:/run/secrets/slack-app-token"
];
ExecStartPre = pkgs.writeShellScript "generate-matterbridge-config" ''
set -euo pipefail
SLACK_TOKEN=$(cat $CREDENTIALS_DIRECTORY/slack-bot-token)
SLACK_APP_TOKEN=$(cat $CREDENTIALS_DIRECTORY/slack-app-token)
cat > /var/lib/musiclink-matterbridge/matterbridge.toml <<EOF
[api.local]
BindAddress="127.0.0.1:${toString cfg.matterbridge.port}"
Token="musiclink-internal-token"
[[slack]]
[slack.my-slack]
Token="$SLACK_TOKEN"
AppToken="$SLACK_APP_TOKEN"
# Socket Mode is cleaner (no public ingress needed)
# If using Socket Mode, we usually need app-level token.
# Matterbridge supports it? It seems to use standard Slack API.
# Let's assume standard RTM or Events API if not specified.
# Wait, for standard Slack bot we need 'Token'. 'AppToken' is for Socket Mode.
[[gateway]]
name = "musiclink-gateway"
enable = true
[[gateway.inout]]
account = "api.local"
[[gateway.inout]]
account = "slack.my-slack"
channel = "${cfg.matterbridge.slackChannel}"
EOF
'';
ExecStart = "${pkgs.matterbridge}/bin/matterbridge -conf /var/lib/musiclink-matterbridge/matterbridge.toml";
Restart = "always";
RestartSec = "10s";
};
};
# -------------------------------------------------------------------------
# MusicLink Bot Service
# -------------------------------------------------------------------------
systemd.services.musiclink = {
description = "MusicLink Bot";
after = [ "musiclink-matterbridge.service" ];
wants = [ "musiclink-matterbridge.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = "musiclink";
WorkingDirectory = "/var/lib/musiclink";
ExecStartPre = pkgs.writeShellScript "generate-musiclink-config" ''
cat > /var/lib/musiclink/config.toml <<EOF
[matterbridge]
url = "http://127.0.0.1:${toString cfg.matterbridge.port}/api/websocket"
token = "musiclink-internal-token"
gateway = "musiclink-gateway"
username = "MusicLink"
EOF
'';
ExecStart = "${cfg.package}/bin/musiclink";
Restart = "always";
RestartSec = "10s";
};
};
};
}

View file

@ -4,7 +4,7 @@
# Run with: nix flake check (as part of checks)
# Or standalone: nix build .#checks.x86_64-linux.vm-integration
{ pkgs, pkgs-unstable, opencode }:
{ pkgs, pkgs-unstable, opencode, musiclink }:
pkgs.nixosTest {
name = "ops-jrz1-integration";
@ -23,6 +23,7 @@ pkgs.nixosTest {
../modules/mautrix-whatsapp.nix
../modules/mautrix-gmessages.nix
../modules/maubot.nix
../modules/musiclink.nix
../modules/dev-services.nix
../modules/security/fail2ban.nix
../modules/security/ssh-hardening.nix
@ -30,7 +31,7 @@ pkgs.nixosTest {
# Provide pkgs-unstable to modules that need it
_module.args = {
inherit pkgs-unstable opencode;
inherit pkgs-unstable opencode musiclink;
};
# VM-specific settings