Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/nix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Nix

on:
pull_request:
paths:
- "flake.nix"
- "flake.lock"
- "nix/**"
- ".github/workflows/nix.yml"
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

# WHY path-filtered on PRs: `nix flake check` builds the full workspace plus
# two NixOS VM module tests, which is far too heavy for every PR. Main always
# runs it — the flake is a release surface (#610). No store cache action:
# magic-nix-cache requires FlakeHub credentials the repo does not carry.
jobs:
flake-check:
name: nix flake check
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22
- name: nix flake check
run: nix flake check --print-build-logs
112 changes: 112 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 31 additions & 20 deletions nix/module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ let
downloadDir = cfg.settings.ergasia.download_dir or "/data/downloads";
podcastDir = cfg.settings.komide.podcast_dir or "/data/podcasts";
in {
# WHY: current nixpkgs ships an unrelated binary-cache server under the
# same services.harmonia namespace (nixos/modules/services/networking/
# harmonia.nix); importing both declares the option path twice. This flake's
# module is the media platform — the upstream one is disabled here.
disabledModules = [ "services/networking/harmonia.nix" ];

options.programs.harmonia-desktop = {
enable = lib.mkEnableOption "Harmonia desktop application";

Expand Down Expand Up @@ -168,26 +174,31 @@ in {
networking.firewall.allowedTCPPorts =
lib.mkIf cfg.openFirewall [ (cfg.settings.paroche.port or 8096) ];
} // lib.mkIf desktopCfg.enable {
# Desktop application launcher entry.
xdg.desktopEntries.harmonia = {
name = "Harmonia";
genericName = "Music Player";
comment = "Self-hosted music, podcasts, and audiobooks";
exec = "${desktopCfg.package}/bin/harmonia %U";
icon = "harmonia";
categories = [ "Audio" "Music" "Player" "AudioVideo" ];
startupNotify = true;
mimeType = lib.optionals desktopCfg.installMimeTypes [
"audio/flac"
"audio/mpeg"
"audio/mp4"
"audio/ogg"
"audio/opus"
"audio/wav"
"audio/aac"
"x-scheme-handler/harmonia"
];
};
# Desktop application launcher entry. NixOS has no xdg.desktopEntries
# option (that namespace is Home Manager); a system-wide launcher is a
# package carrying share/applications/*.desktop.
environment.systemPackages = [
(pkgs.makeDesktopItem {
name = "harmonia";
desktopName = "Harmonia";
genericName = "Music Player";
comment = "Self-hosted music, podcasts, and audiobooks";
exec = "${desktopCfg.package}/bin/harmonia %U";
icon = "harmonia";
categories = [ "Audio" "Music" "Player" "AudioVideo" ];
startupNotify = true;
mimeTypes = lib.optionals desktopCfg.installMimeTypes [
"audio/flac"
"audio/mpeg"
"audio/mp4"
"audio/ogg"
"audio/opus"
"audio/wav"
"audio/aac"
"x-scheme-handler/harmonia"
];
})
];

# D-Bus service file — allows the desktop environment to activate
# Harmonia for MPRIS without it already running.
Expand Down
37 changes: 24 additions & 13 deletions nix/modules/harmonia-dac.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ let
# Sources: HiFiBerry docs, IQaudio docs, Raspberry Pi overlay index.
overlayMap = {
# HiFiBerry DAC+ (standard, non-HD)
hifiberry-dacplus = { overlay = "hifiberry-dacplus"; alsaCard = "sndrpihifiberry"; };
hifiberry-dacplus = { overlayFile = "hifiberry-dacplus"; alsaCard = "sndrpihifiberry"; };
# HiFiBerry DAC2 HD uses the "adcpro" overlay (same silicon, extra ADC channels)
hifiberry-dac2hd = { overlay = "hifiberry-dacplusadcpro"; alsaCard = "sndrpihifiberry"; };
hifiberry-dac2hd = { overlayFile = "hifiberry-dacplusadcpro"; alsaCard = "sndrpihifiberry"; };
# IQaudio DAC+ (standard)
iqaudio-dacplus = { overlay = "iqaudio-dacplus"; alsaCard = "IQaudIODAC"; };
# IQaudio DAC Pro uses the same overlay with auto-detection flag
iqaudio-dacpro = { overlay = "iqaudio-dacplus,auto"; alsaCard = "IQaudIODAC"; };
iqaudio-dacplus = { overlayFile = "iqaudio-dacplus"; alsaCard = "IQaudIODAC"; };
# IQaudio DAC Pro — same base overlay. Upstream recommends the ",auto"
# parameter for Pro auto-detection; hardware.deviceTree.overlays cannot
# express overlay parameters, so the Pro is driven by the base overlay
# and detection should be verified on the hardware.
iqaudio-dacpro = { overlayFile = "iqaudio-dacplus"; alsaCard = "IQaudIODAC"; };
};

selected = overlayMap.${dacCfg.model};
Expand All @@ -43,16 +46,24 @@ in {
};

config = lib.mkIf (cfg.enable && dacCfg.enable) {
# Enable the DAC overlay and disable onboard audio on Pi 4.
# Pi 5 uses a compatible mechanism — see nix/README.md for Pi 5 notes.
hardware.raspberry-pi."4" = {
dt-overlays.${selected.overlay}.enable = true;

# WHY: Onboard BCM audio and the DAC HAT share the I2S bus;
# both cannot run simultaneously.
audio.enable = false;
# Current-nixpkgs Pi device-tree configuration: the
# hardware.raspberry-pi."4" namespace was removed upstream, so the DAC
# overlay is applied through the generic hardware.deviceTree mechanism
# (the dtbo ships in the rpi kernel's dtbs/overlays directory).
hardware.deviceTree = {
enable = true;
overlays = [{
name = selected.overlayFile;
dtboFile = "${config.boot.kernelPackages.kernel}/dtbs/overlays/${selected.overlayFile}.dtbo";
}];
};

# WHY: onboard BCM audio and the DAC HAT share the I2S bus and cannot
# run simultaneously; the removed hardware.raspberry-pi audio switch
# did this by withholding the overlay, and the current-API equivalent
# is to keep the onboard audio driver from loading.
boot.blacklistedKernelModules = [ "snd_bcm2835" ];

# Set the DAC as the ALSA default card system-wide.
environment.etc."asound.conf".text = ''
pcm.!default {
Expand Down
8 changes: 4 additions & 4 deletions nix/modules/harmonia-render.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let
};

dsp = {
volume.level_db = 20.0 * (builtins.log cfg.dsp.volume / builtins.log 10.0);
volume.level_db = cfg.dsp.levelDb;
replaygain = {
enabled = cfg.dsp.replayGain != "off";
mode = if cfg.dsp.replayGain == "album" then "album" else "track";
Expand Down Expand Up @@ -101,10 +101,10 @@ in {
};

dsp = {
volume = lib.mkOption {
levelDb = lib.mkOption {
type = lib.types.float;
default = 1.0;
description = "Linear volume multiplier (1.0 = 0 dBFS, 0.5 = -6 dBFS).";
default = 0.0;
description = "Output level in dBFS (0.0 = full scale, -6.0 = half amplitude). Rendered directly as the engine's volume.level_db.";
};

replayGain = lib.mkOption {
Expand Down
2 changes: 1 addition & 1 deletion nix/tests/module-test.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{ pkgs }:

pkgs.nixosTest {
pkgs.testers.nixosTest {
name = "harmonia-basic";

nodes.server = { ... }: {
Expand Down
2 changes: 1 addition & 1 deletion nix/tests/render-module-test.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ let
}
)).config;

in pkgs.nixosTest {
in pkgs.testers.nixosTest {
name = "harmonia-render-module";

nodes.machine = { ... }: {
Expand Down
Loading