diff --git a/.beads/.local_version b/.beads/.local_version index a8ab6c9..650298f 100644 --- a/.beads/.local_version +++ b/.beads/.local_version @@ -1 +1 @@ -0.44.0 +0.47.1 diff --git a/.beads/last-touched b/.beads/last-touched index 6d3ebae..1b9fc85 100644 --- a/.beads/last-touched +++ b/.beads/last-touched @@ -1 +1 @@ -ops-jrz1-6ip +ops-jrz1-k3e0 diff --git a/.claude/skills/orch b/.claude/skills/orch index 12b0e3b..3cacd0b 120000 --- a/.claude/skills/orch +++ b/.claude/skills/orch @@ -1 +1 @@ -/nix/store/c1qr5g1z0n91x5qw9cpc2wyk66hsavkh-ai-skill-orch \ No newline at end of file +/nix/store/5s89k0jw8xn415n29li5qd2z9wm1zy6r-ai-skill-orch \ No newline at end of file diff --git a/.claude/skills/worklog b/.claude/skills/worklog index 9e01618..8e79ede 120000 --- a/.claude/skills/worklog +++ b/.claude/skills/worklog @@ -1 +1 @@ -/nix/store/pnw3i2sn988jv6dwjps7ilvlniz76pbn-ai-skill-worklog \ No newline at end of file +/nix/store/xg0z64szmizn99yw8psrd9ffsiy2mfxi-ai-skill-worklog \ No newline at end of file diff --git a/.gemini/skills/orch b/.gemini/skills/orch new file mode 120000 index 0000000..3cacd0b --- /dev/null +++ b/.gemini/skills/orch @@ -0,0 +1 @@ +/nix/store/5s89k0jw8xn415n29li5qd2z9wm1zy6r-ai-skill-orch \ No newline at end of file diff --git a/.gemini/skills/worklog b/.gemini/skills/worklog new file mode 120000 index 0000000..8e79ede --- /dev/null +++ b/.gemini/skills/worklog @@ -0,0 +1 @@ +/nix/store/xg0z64szmizn99yw8psrd9ffsiy2mfxi-ai-skill-worklog \ No newline at end of file diff --git a/flake.nix b/flake.nix index a2f4de3..e7aa8d6 100644 --- a/flake.nix +++ b/flake.nix @@ -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 diff --git a/hosts/ops-jrz1-vm.nix b/hosts/ops-jrz1-vm.nix index 3abd548..fd69b35 100644 --- a/hosts/ops-jrz1-vm.nix +++ b/hosts/ops-jrz1-vm.nix @@ -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 diff --git a/hosts/ops-jrz1.nix b/hosts/ops-jrz1.nix index 4c5f6b2..8d74695 100644 --- a/hosts/ops-jrz1.nix +++ b/hosts/ops-jrz1.nix @@ -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) diff --git a/modules/dev-services.nix b/modules/dev-services.nix index caa2370..0bd2056 100644 --- a/modules/dev-services.nix +++ b/modules/dev-services.nix @@ -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; diff --git a/modules/musiclink.nix b/modules/musiclink.nix new file mode 100644 index 0000000..2c8c707 --- /dev/null +++ b/modules/musiclink.nix @@ -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 < /var/lib/musiclink/config.toml <