Skip to content

It’s not “just dotfiles” - it's extreme modularity that looks and feels like a NixOS flake. It's Voice-driven DevOps, it's a declarative home automation system, it's a self-contained auto-documenting monster.

License

Notifications You must be signed in to change notification settings

QuackHack-McBlindy/dotfiles

Repository files navigation

❄️🦆 QuackHack-McBLindy NixOS dotfiles

NixOS License Linux Kernel GNOME Bash Python Rust Nix

Sponsors

This is a automagiduckically updated README.md

Caution

Do not blindly run this flake.
That's my job. 🧑‍🦯


Here lives home machines configurations,
and home automations, fully reproducible,
crafted as a tiny Nix flake
Glued together by a Nix-flavoured command line utility,
easiy expanded and used to deploy, doc, and duck around 🦆✨

What makes this configuration unique?

Nix declarative configuration style, custom modules evaluated dynamically for each host.
Home Manager - No ducking way! I just auto symlink ./home to /home
Zigbee and smart home tightly integrated with Nix. For not just a declarative house but also deployable apartments.
Not only that - voice assistant is LIGHTNIGHT FAST! (ms) ⚡🏆

  • 97 qwacktastic scripts in /bin - 58 scripts have voice commands.
  • 2440 dynamically generated regex patterns - makes 294355045 phrases available as commands.
  • Smart Home Nix Style - Managing 2 TV's, 41 devices & 6 scenes.
  • Natural Language support with complete voice pipeline
  • Infra as everyday accessibility
  • Yubikey encrypted deployment system
  • Self Documenting

List would get long, very quackly.
perhaps a more suitable question would be:
"What makes this configuration common?"


❄️ Flake

Define yourself at `config.this.user.me`.
{
  discord = "https://discordapp.com/users/675530282849533952";
  dotfilesDir = "/home/pungkula/dotfiles";
  email = "isthisrandomenough@protonmail.com";
  extraGroups = [ "networkmanager" "wheel" "dialout" "docker" "dockeruser" "users" "pungkula" "adbusers" "audio" "2000" ];
  hashedPassword = "$y$j9T$m8hPD36i1VMaO5rurbZ4j0$KpzQyat.F6NoWFKpisEj77TvpN2wBGB8ezd26QoKDj6";
  matrix = "";
  mobileDevices =   {
    iphone =     {
      pubkey = "UFB0T1Y/uLZi3UBtEaVhCi+QYldYGcOZiF9KKurC5Hw=";
      wgip = "10.0.0.7"
    };
    tablet =     {
      pubkey = "ETRh93SQaY+Tz/F2rLAZcW7RFd83eofNcBtfyHCBWE4=";
      wgip = "10.0.0.8"
    };
  };
  name = "pungkula";
  repo = "git@github.com:QuackHack-McBlindy/dotfiles.git"
};
Define each hosts data at `config.this.host`.
{
  hostname = "desktop";
  interface = [ "enp119s0" ];
  ip = "192.168.1.111";
  keys =   {
    privateKeys =     {
    };
    publicKeys =     {
      adb = "QAAAACEJNfsfRV4PQ9Ah87MbTVbMkbXC6CAMDOR+0K6mIpv/4TSzYMkc2qit3Kryc55IVOjwR3fJRjj/uL549gZ7nEemWtcd3AsYQBp0iIEor8nu1L/V6jfsTY6Xe/pl06xoroy6OwZRWuDbZ4wD2xQRRQjfPd+JtYnMAWneM6r1V15uR67w4ITvjk3ckyfgNeLZMUwahMRjC3wSjaU9sAdKNmg8yPd8uHZ+mK6mstxJFAGEpnnm1lE7Z2r0DF6h6MKY1++dwhU+WM5BRDNiBg+D4i6fDW4+Z1I9ENuFnjT17zAxZXch04SNlG3O94BANYP7jmKp60OvtDL6msfphntuIUzMCkndF9De0Kv4lJdQxe1d+wf+AFpmtd/xtrk45YdMV+eWCJf2OkidaHmSj4ffkAobpun0VrkZN2Z1JymmdsvUbyMjAsby3Zun0xr3EocUS8Jy5TcsK/dcpD6CB5dqzlHhsHSAWt2TDwPzZYXgV1xc+q+PqM09OVN1xActJu75UMkg5b84U15hwQvYdwB8UaopMWWk6p064c7gxYSfH7fSxwkW2Jy1CElgJa55Pp4SZG9b/3B+VcNL1WSf6v/lvJqPbrRvBqvS0+e9wcFMNZtQKTX3n5X0wW1/czZPCQX+hmM8Uu1qrtaz4rKViIEGf4YR0/9eUGYQVfuAxAh8ZmsroJlnAAEAAQA= pungkula@desktop";
      age = "age16utg7mmk73cn3glrwthtm0p7mf6g3vrd48h3ucpn6wnf28pgxvcsh4rjjp";
      borg = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMVYczAOBSeS7WfSvzYDOS4Q9Ss+yxCf2G5MVfAALOx/";
      builder = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINQ7c/AeIpmJS6cWQkHOe4ZEq3DXVRnjtTWuWfx6L46n";
      cache = "cache:/pbj1Agw2OoSSDZcClS69RHa1aNcwwTOX3GIEGKYwPc=";
      host = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdwPkRQxlbrbRGwEO5zMJ4m+7QqUQPZg1iqbd5HRP34";
      iPhone = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMcmr+z7k/yCbrFg+JDgo8JCuWqNVYn10ajRbNTp8fq";
      ssh = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPwZL27kGTQDIlSe03abT9F24nSAizORyjo5cI3BD92s";
      wireguard = "Oq0ZaYAnOo5sLpV//OEFwLgjVCxPyeQqf8cZBASluWk="
    };
  };
  modules =   {
    hardware = [ "cpu/intel" "gpu/amd" "audio" ];
    networking = [ "default" "pool" ];
    programs = [ "default" "thunar" "firefox" "vesktop" ];
    services = [ "ssh" "default" "adb" "backup" "cache" "keyd" "jelly" "duck-tv" ];
    system = [ "nix" "pkgs" "gnome" "crossEnv" "gtk" ];
    virtualisation = [ "docker" "vm" ]
  };
  system = "x86_64-linux";
  wgip = "10.0.0.2"
};
Define any optional theme configuration at `config.this.theme`.
{
  cursorTheme =   {
    name = "Bibata-Modern-Classic";
    package = "/nix/store/1np4cfqil5jh04zmscj3i6h2zvh9yqvv-bibata-cursors-2.0.7";
    size = 32
  };
  enable = false;
  fonts =   {
    monospace = "Fira Code";
    packages = [ "/nix/store/k4s2ckig2pyi2lzzaxmh8wcwbq7n7pz3-fira-code-6.2" ];
    system = "Fira Sans"
  };
  gtkSettings =   {
    gtk-application-prefer-dark-theme = "1";
    gtk-cursor-theme-name = "Bibata-Modern-Classic";
    gtk-icon-theme-name = "elementary-xfce-icon-theme"
  };
  iconTheme =   {
    name = "Papirus-Dark";
    package = "/nix/store/5ncf05fvvy7zmb2azprzq1qhymwh733h-papirus-icon-theme-20250201"
  };
  name = "gtk3.css";
  styles = "/nix/store/fy9zcsirdc47lyshwmfk1flpc4mgrxvw-source/modules/themes/css/gtk3.css"
};
Define Zigbee-devices, scenes, automations, tv's, channels, etc at `config.house`.
# dotfiles/modules/myHouse.nix ⮞ https://github.com/quackhack-mcblindy/dotfiles
{ # 🦆 says ⮞ my house - qwack 
  config, # 🦆 says ⮞ more info ⮞ https://quackhack-mcblindy.github.io/blog/house/index.html
  lib,
  self,
  pkgs,
  ...
} : let # 🦆 duck say ⮞ icon map
  icons = {
    light = {
      ceiling         = "mdi:ceiling-light";
      strip           = "mdi:light-strip";
      spotlight       = "mdi:spotlight";
      bulb            = "mdi:lightbulb";
      bulb_color      = "mdi:lightbulb-multiple";
      desk            = "mdi:desk-lamp";
      floor           = "mdi:floor-lamp";
      wall            = "mdi:wall-sconce-round";
      chandelier      = "mdi:chandelier";
      pendant         = "mdi:vanity-light";
      nightlight      = "mdi:lightbulb-night";
      strip_rgb       = "mdi:led-strip-variant";
      reading         = "mdi:book-open-variant";
      candle          = "mdi:candle";
      ambient         = "mdi:weather-night";
    };
    sensor = {
      motion          = "mdi:motion-sensor";
      smoke           = "mdi:smoke-detector";
      water           = "mdi:water";
      contact         = "mdi:door";
      temperature     = "mdi:thermometer";
      humidity        = "mdi:water-percent";
    };
    remote            = "mdi:remote";
    outlet            = "mdi:power-socket-eu";
    dimmer            = "mdi:toggle-switch";
    pusher            = "mdi:gesture-tap-button";
    blinds            = "mdi:blinds";
  };
  
  health = lib.mapAttrs (hostName: _: {
    enable = true;
    description = "Health Check: ${hostName}";
    topic = "zigbee2mqtt/health/${hostName}";
    actions = [
      {
         type = "shell";
         command = ''
           mkdir -p /var/lib/zigduck/health
           touch /var/lib/zigduck/health/${hostName}.json
           echo "$MQTT_PAYLOAD" > /var/lib/zigduck/health/${hostName}.json
        '';
       }
     ];
  }) self.nixosConfigurations;
  
in { # 🦆 duck say ⮞ qwack
  house = {
    # 🦆 says ⮞ ROOM CONFIGURATION
    rooms = {
      bedroom.icon    = "mdi:bed";
      hallway.icon    = "mdi:door";
      kitchen.icon    = "mdi:food-fork-drink";
      livingroom.icon = "mdi:sofa";
      wc.icon         = "mdi:toilet";
      other.icon      = "mdi:misc";
    };  

    # 🦆 says ⮞ DASHBOARD CONFIOGURATION 
    dashboard = {
      statusCards = {
        xmr = {
          enable = true;
          title = "XMR";
          icon = "fab fa-monero";
          color = "#ff6600";
          filePath = "/var/lib/zigduck/xmr.json";
          jsonField = "current_price";
          format = "${value}";
          detailsJsonField = "24h_change";
          detailsFormat = "24h: {value}%";
        };

        btc = {
          enable = true;
          title = "BTC";
          icon = "fab fa-bitcoin";
          color = "#ff6600";
          filePath = "/var/lib/zigduck/btc.json";
          jsonField = "current_price";
          format = "${value}";
          detailsJsonField = "24h_change";
          detailsFormat = "24h: {value}%";
        };

        energy = {
          enable = true;
          title = "Energy";
          icon = "fas fa-bolt";
          color = "#4caf50";
          filePath = "/var/lib/zigduck/energy.json";          
          jsonField = "current_price";
          format = "{value} SEK/kWh";          
          detailsJsonField = "monthly_usage";
          detailsFormat = "Usage: {value} kWh";
        };

      };
      
      pages = {
        "4" = {
          icon = "fas fa-notes-medical";
          title = "health";
          files = { health = "/var/lib/zigduck/health"; };
          css = ''

            .health-page .container,
            .health-page .content,
            .health-page > div {
              width: 100% !important;
              max-width: 100% !important;
              margin: 0 !important;
              padding: 0 !important;
            }

            .page[data-page] {
              width: 100% !important;
              max-width: 100% !important;
            }

            .health-page {
              max-width: 1200px;
              margin: 0 auto;
              padding: 20px;
            }
            
            .health-grid {
              display: grid;
              grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
              gap: 15px;
              justify-items: center;
            }
            
            .health-card {
              background: var(--card-bg);
              border-radius: 12px;
              padding: 20px;
              box-shadow: var(--card-shadow);
              width: 100%;
              max-width: 350px;
            }     
            .health-card-header {
              display: flex;
              justify-content: space-between;
              align-items: center;
              margin-bottom: 15px;
              border-bottom: 1px solid var(--border-color);
              padding-bottom: 10px;
              flex-direction: column;
              text-align: center;
              gap: 10px;
            }
            .health-hostname {
              font-size: 1.2rem;
              font-weight: bold;
              color: var(--primary);
            }     
            .health-status {
              display: grid;
              gap: 8px;
            }    
            .health-item {
              display: flex;
              justify-content: space-between;
              align-items: center;
            }   
            .health-label {
              color: var(--gray);
              font-size: 0.9rem;
            }        
            .health-value {
              font-weight: 600;
            } 
            .status-good { color: #2ecc71; }
            .status-warning { color: #f39c12; }
            .status-critical { color: #e74c3c; }
            
            /* 🦆 says ⮞ Responsive design */
            @media (max-width: 768px) {
              .health-page {
                padding: 10px;
              }
              
              .health-grid {
                grid-template-columns: 1fr;
                gap: 10px;
              }
              
              .health-card {
                max-width: 100%;
              }
            }           
          '';
          code = ''

            <h1 style="text-align:center;">Machines Health</h1>
            <div id="healthContainer" class="health-grid"></div>


            <script>
              async function loadHealthData() {
                try {
                  const response = await fetch('/health/');
                  const text = await response.text();
                  
                  const parser = new DOMParser();
                  const htmlDoc = parser.parseFromString(text, 'text/html');
                  const links = Array.from(htmlDoc.querySelectorAll('a'));
                  const jsonFiles = links
                    .map(link => link.href)
                    .filter(href => href.endsWith('.json'))
                    .map(href => href.split('/').pop());
                  
                  console.log('Found health files:', jsonFiles);
                  
                  const container = document.getElementById('healthContainer');
                  container.innerHTML = "";
                  
                  for (const file of jsonFiles) {
                    try {
                      const healthResponse = await fetch('/health/' + file);
                      const healthData = await healthResponse.json();
                      createHealthCard(healthData, container);
                    } catch (error) {
                      console.error('Error loading health file:', file, error);
                    }
                  }
                  
                } catch (error) {
                  console.error('Error loading health directory:', error);
                  document.getElementById('healthContainer').innerHTML = 
                    '<div class="error">Unable to load health data</div>';
                }
              }
              
              function createHealthCard(data, container) {
                const card = document.createElement('div');
                card.className = 'health-card';
                
                const status = calculateOverallStatus(data);
                
                card.innerHTML = `
                  <div class="health-card-header">
                    <div class="health-hostname"><strong><h1>''${data.hostname}</h1></strong></div><br>
                    <div class="health-uptime">''${data.uptime}</div>
                  </div>
                  <div class="health-status">
                    <div class="health-item">
                      <span class="health-label"><strong>CPU:</strong></span>
                      <span class="health-value ''${getCPUStatusClass(data.cpu_usage)}">''${data.cpu_usage}%</span>
                    </div>
                    <div class="health-item">
                      <span class="health-label"><strong>Memory:</strong></span>
                      <span class="health-value ''${getMemoryStatusClass(data.memory_usage)}">''${data.memory_usage}%</span>
                    </div>
                    <div class="health-item">
                      <span class="health-label"><strong>CPU 🌡️:</strong></span>
                      <span class="health-value ''${getTempStatusClass(data.cpu_temperature)}">''${data.cpu_temperature}</span>
                    </div>
                    ''${createDiskUsageHTML(data.disk_usage)}
                    ''${createDiskTempHTML(data.disk_temperature)}
                  </div>
                `;
                
                container.appendChild(card);
              }
              
              function calculateOverallStatus(data) {
                if (data.cpu_usage > 90 || data.memory_usage > 90) return 'critical';
                if (data.cpu_usage > 80 || data.memory_usage > 80) return 'warning';
                return 'good';
              }
              
              function getCPUStatusClass(usage) {
                if (usage > 80) return 'status-critical';
                if (usage > 60) return 'status-warning';
                return 'status-good';
              }
              
              function getMemoryStatusClass(usage) {
                if (usage > 90) return 'status-critical';
                if (usage > 75) return 'status-warning';
                return 'status-good';
              }
              
              function getTempStatusClass(temp) {
                const tempValue = parseFloat(temp);
                if (tempValue > 70) return 'status-critical';
                if (tempValue > 60) return 'status-warning';
                return 'status-good';
              }
              
              function createDiskUsageHTML(diskUsage) {
                if (!diskUsage) return "";
                return Object.entries(diskUsage).map(([device, usage]) => `
                  <div class="health-item">
                    <span class="health-label"><strong>Disk</strong> (''${device}):</span>
                    <span class="health-value ''${getDiskStatusClass(usage)}">''${usage}</span>
                  </div>
                `).join("");
              }
              
              function createDiskTempHTML(diskTemp) {
                if (!diskTemp) return "";
                return Object.entries(diskTemp).map(([device, temp]) => `
                  <div class="health-item">
                    <span class="health-label"><strong>Disk 🌡️</strong>(''${device}):</span>
                    <span class="health-value ''${getTempStatusClass(temp)}">''${temp}</span>
                  </div>
                `).join("");
              }
              
              function getDiskStatusClass(usage) {
                const usageValue = parseFloat(usage);
                if (usageValue > 90) return 'status-critical';
                if (usageValue > 80) return 'status-warning';
                return 'status-good';
              }
              
              document.addEventListener('DOMContentLoaded', function() {
                if (document.getElementById('healthContainer')) {
                  loadHealthData();
                  setInterval(loadHealthData, 30000);
                }
              });
            </script>                
          '';
        };
    
      };
    };
  
# 🦆 ⮞ ZIGBEE ⮜ 🐝
    zigbee = {
      # 🦆 says ⮞ encrypted zigbee network key
      networkKeyFile = config.sops.secrets.z2m_network_key.path;
      
      # 🦆 says ⮞ mosquitto authentication
      mosquitto = {
        username = "mqtt";
        passwordFile = config.sops.secrets.mosquitto.path;
      };
      
      # 🦆says⮞ coordinator configuration
      coordinator = {
        vendorId =  "10c4";
        productId = "ea60";
        symlink = "zigbee"; # 🦆 says ⮞ diz symlinkz da serial port to /dev/zigbee
      };
    
      # 🦆 says ⮞ when motion triggers lights
      darkTime = {
        enable = true;
        after = "14";
        before = "9";
        duration = "900";
      };
      
  # 🦆 ⮞ AUTOMATIONS ⮜
      automations = {  
      # 🦆 says ⮞ there are 6 different automation types
        # 🦆 says ⮞ + a greeting automation
        greeting = {
          enable = true;
          awayDuration = "7200";
          greeting = "Borta bra, hemma bäst. Välkommen idiot! ";
          delay = "10";
          sayOnHost = "desktop";
        };
        
        # 🦆 says ⮞ 1. mqtt triggered automations
        mqtt_triggered = {
          xmr = {
            enable = true;
            description = "Writes XMR data to file for dashboard";
            topic = "zigbee2mqtt/crypto/xmr/price";
            actions = [
              {
                type = "shell";
                command = ''
                  touch /var/lib/zigduck/xmr.json
                  echo "$MQTT_PAYLOAD" > /var/lib/zigduck/xmr.json
                '';
              }
            ];
          };
          
          btc = {
            enable = true;
            description = "Writes BTC data to file for dashboard";
            topic = "zigbee2mqtt/crypto/btc/price";
            actions = [
              {
                type = "shell";
                command = ''
                  touch /var/lib/zigduck/btc.json
                  echo "$MQTT_PAYLOAD" > /var/lib/zigduck/btc.json
                '';
              }
            ];
          };

          energy = {
            enable = true;
            description = "Writes tibber data to file for dashboard";
            topic = "zigbee2mqtt/tibber/energy";
            actions = [
              {
                type = "shell";
                command = ''
                  touch /var/lib/zigduck/energy.json
                  echo "$MQTT_PAYLOAD" > /var/lib/zigduck/energy.json
                  current_price=$(echo "$MQTT_PAYLOAD" | jq -r '.current_price' | sed 's/"//g')
                  # 🦆says⮞ notify if high energy price
                  if [ $(echo "$current_price > 2.0" | bc -l) -eq 1 ]; then
                    yo notify "⚡ High energy price: $current_price SEK/kWh"
                  fi
                '';
              }
            ];
          };
        } // health; 
        
        # 🦆 says ⮞ 2. room action automations
        room_actions = {
          hallway = { 
            door_opened = [];
            door_closed = [];
          };  
          bedroom = { 
            # 🦆 says ⮞ default actions already configured - room lights will turn on upon motion (if darkTime)
            motion_detected = [
              {
                type = "scene";
                scene = "Chill Scene";
              }           
            ];
            motion_not_detected = [
              {
                type = "mqtt";
                topic = "zigbee2mqtt/Sänggavel/set";
                message = ''{"state":"OFF", "brightness": 80}'';
              }              
            ];
          };
        };
          
        # 🦆 says ⮞ 3. global actions automations  
        global_actions = {
          leak_detected = [
            {
              type = "shell";
              command = "yo notify '🚨 WATER LEAK DETECTED!'";
            }
          ];
          smoke_detected = [
            {
              type = "shell";
              command = "yo notify '🔥 SMOKE DETECTED!'";
            }
          ];
        };

        # 🦆 says ⮞ 4. dimmer actions automations
        dimmer_actions = {          
          bedroom = {
            on_hold_release = {
              enable = true;
              description = "Turn off all configured light devices";
              extra_actions = [];
              override_actions = [];
            };

            off_hold_release = {
              enable = true;
              description = "Turn off all configured light devices";
              extra_actions = [];
              override_actions = [
                {
                  type = "scene";
                  command = "dark";
                }
              ];
            };   
          };              
        };
        
        # 🦆 says ⮞ 5. time based automations
        time_based = {};
        
        # 🦆 says ⮞ 6. presence based automations
        presence_based = {};
        
      };  


  # 🦆 ⮞ DEVICES ⮜      
      devices = { 
        # 🦆 says ⮞ Kitchen   
        "0x0017880103ca6e95" = { # 🦆 says ⮞ 64bit IEEE adress (this is the unique device ID)  
          friendly_name = "Dimmer Switch Kök"; # 🦆 says ⮞ simple human readable friendly name
          room = "kitchen"; # 🦆 says ⮞ bind to group
          type = "dimmer"; # 🦆 says ⮞ set a custom device type
          icon = icons.dimmer;
          endpoint = 1; # 🦆 says ⮞ endpoint to call the device on
          batteryType = "CR2450"; # 🦆 says ⮞ optional yo
        }; 
        "0x0017880102f0848a" = { 
          friendly_name = "Spotlight kök 1";
          room = "kitchen";
          type = "light";
          icon = icons.light.spotlight;
          endpoint = 11;
        };
        "0x0017880102f08526" = { friendly_name = "Spotlight Kök 2"; room = "kitchen"; type = "light"; icon = icons.light.spotlight; endpoint = 11; };
        "0x0017880103a0d280" = { friendly_name = "Uppe"; room = "kitchen"; type = "light"; icon = icons.light.strip; endpoint = 11; supports_color = true; };
        "0x0017880103e0add1" = { friendly_name = "Golvet"; room = "kitchen"; type = "light"; icon = icons.light.strip; endpoint = 11; supports_color = true; };
        "0xa4c13873044cb7ea" = { friendly_name = "Kök Bänk Slinga"; room = "kitchen"; type = "light"; icon = icons.light.strip; endpoint = 11; };
        "0x70ac08fffe9fa3d1" = { friendly_name = "Motion Sensor Kök"; room = "kitchen"; type = "motion"; icon = icons.sensor.motion; endpoint = 1; batteryType = "CR2032"; }; 
        "0xa4c1380afa9f7f3e" = { friendly_name = "Smoke Alarm Kitchen"; room = "kitchen"; type = "sensor"; icon = icons.sensor.smoke; endpoint = 1; };
        "0x0c4314fffe179b05" = { friendly_name = "Fläkt"; room = "kitchen"; type = "outlet"; icon = icons.outlet; endpoint = 1; };    
        # 🦆 says ⮞ LIVING ROOM
        "0x0017880104f78065" = { friendly_name = "Dimmer Switch Vardagsrum"; room = "livingroom"; type = "dimmer"; icon = icons.dimmer; endpoint = 1; batteryType = "CR2450"; };
        "0x00178801037e754e" = { friendly_name = "Takkrona 1"; room = "livingroom"; type = "light"; icon = icons.light.chandelier; endpoint = 1; supports_color = true; };   
        "0x0017880103c73f85" = { friendly_name = "Takkrona 2"; room = "livingroom"; type = "light"; icon = icons.light.chandelier; endpoint = 1; supports_color = true; };  
        "0x0017880103f94041" = { friendly_name = "Takkrona 3"; room = "livingroom"; type = "light"; icon = icons.light.chandelier; endpoint = 1; supports_color = true; };                  
        "0x0017880103c753b8" = { friendly_name = "Takkrona 4"; room = "livingroom"; type = "light"; icon = icons.light.chandelier; endpoint = 1; supports_color = true; };  
        "0x54ef4410003e58e2" = { friendly_name = "Roller Shade"; room = "livingroom"; type = "blind"; icon = icons.blinds; endpoint = 1; };
        "0x0017880104540411" = { friendly_name = "PC"; room = "livingroom"; type = "light"; icon = icons.light.spotlight; endpoint = 11; supports_color = true; };
        "0x0017880102de8570" = { friendly_name = "Rustning"; room = "livingroom"; type = "light"; icon = icons.light.spotlight; endpoint = 11; supports_color = true; };
        "0x540f57fffe85c9c3" = { friendly_name = "Water Sensor"; room = "livingroom"; type = "sensor"; icon = icons.sensor.water; endpoint = 1; };
        # 🦆 says ⮞ HALLWAY
        "0x00178801021311c4" = { friendly_name = "Motion Sensor Hall"; room = "hallway"; type = "motion"; icon = icons.sensor.motion; endpoint = 1; batteryType = "AAA"; };#⮜ AAA-AWESOME 🦆 
        "0x00158d00053ec9b1" = { friendly_name = "Door Sensor Hall"; room = "hallway"; type = "sensor"; icon = icons.sensor.contact; endpoint = 1; };
        "0x0017880103eafdd6" = { friendly_name = "Tak Hall";  room = "hallway"; type = "light"; icon = icons.light.ceiling; supports_color = true; endpoint = 11; };
        "0x000b57fffe0e2a04" = { friendly_name = "Vägg"; room = "hallway"; type = "light"; icon = icons.light.wall; supports_temperature = true; endpoint = 1; };
        # 🦆 says ⮞ WC
        "0x001788010361b842" = { friendly_name = "WC 1"; room = "wc"; type = "light"; icon = icons.light.ceiling; supports_temperature = true; endpoint = 11; };
        "0x0017880103406f41" = { friendly_name = "WC 2"; room = "wc"; type = "light"; icon = icons.light.ceiling; supports_temperature = true; endpoint = 11; };
        # 🦆 says ⮞ BEDROOM  
        "0xa4c13832742c96f7" = { friendly_name = "Robot Arm 1"; room = "bedroom"; type = "pusher"; endpoint = 11; icon = icons.pusher; batteryType = "CR02"; };
        "0xa4c138387966b58d" = { friendly_name = "Robot Arm 2"; room = "bedroom"; type = "pusher"; endpoint = 11; icon = icons.pusher; batteryType = "CR02"; };
        "0xa4c1380c0a35052e" = { friendly_name = "Robot Arm 3"; room = "bedroom"; type = "pusher"; endpoint = 11; icon = icons.pusher; batteryType = "CR02"; };
        "0xa4c1381e74b6d2e6" = { friendly_name = "Robot Arm 4"; room = "bedroom"; type = "pusher"; endpoint = 11; icon = icons.pusher; batteryType = "CR02"; };
        "0x0017880104f77d61" = { friendly_name = "Dimmer Switch Sovrum"; room = "bedroom"; type = "dimmer"; icon = icons.dimmer; endpoint = 1; batteryType = "CR2450"; }; 
        "0x0017880106156cb0" = { friendly_name = "Taket Sovrum 1"; room = "bedroom"; type = "light"; icon = icons.light.ceiling; endpoint = 11; supports_color = true; };
        "0x0017880103c7467d" = { friendly_name = "Taket Sovrum 2"; room = "bedroom"; type = "light"; icon = icons.light.ceiling; endpoint = 11; supports_color = true; };
        "0x0017880109ac14f3" = { friendly_name = "Sänglampa"; room = "bedroom"; type = "light"; icon = icons.light.bulb; endpoint = 11; supports_color = true; };
        "0x0017880104051a86" = { friendly_name = "Sänggavel"; room = "bedroom"; type = "light"; icon = icons.light.strip; endpoint = 11; supports_color = true; };
        "0xf4b3b1fffeaccb27" = { friendly_name = "Motion Sensor Sovrum"; room = "bedroom"; type = "motion"; icon = icons.sensor.motion; endpoint = 1; batteryType = "CR2032"; };
        "0x0017880103f44b5f" = { friendly_name = "Dörr"; room = "bedroom"; type = "light"; icon = icons.light.strip; endpoint = 11; supports_color = true; };
        "0x00178801001ecdaa" = { friendly_name = "Bloom"; room = "bedroom"; type = "light"; icon = icons.light.desk; endpoint = 11; supports_color = true; };
        # 🦆 says ⮞ MISCELLANEOUS
        "0xa4c1382553627626" = { friendly_name = "Power Plug"; room = "other"; type = "outlet"; icon = icons.outlet; endpoint = 1; };
        "0xa4c138b9aab1cf3f" = { friendly_name = "Power Plug 2"; room = "other"; type = "outlet"; icon = icons.outlet; endpoint = 1; };
        "0x000b57fffe0f0807" = { friendly_name = "IKEA 5 Dimmer"; room = "other"; type = "remote"; icon = icons.remote; endpoint = 1; };
        "0x70ac08fffe6497be" = { friendly_name = "On/Off Switch 1"; room = "other"; type = "remote"; icon = icons.remote; endpoint = 1; batteryType = "CR2032"; };
        "0x70ac08fffe65211e" = { friendly_name = "On/Off Switch 2"; room = "other"; type = "remote"; icon = icons.remote; endpoint = 1; batteryType = "CR2032"; };
      };
      
  # 🦆 ⮞ SCENES ⮜
      scenes = {
          # 🦆 says ⮞ Scene name
          "Duck Scene" = {
              # 🦆 says ⮞ Device friendly_name
              "PC" = { # 🦆 says ⮞ Device state
                  state = "ON";
                  brightness = 200;
                  color = { hex = "#00FF00"; };
              };
          };
          # 🦆 says ⮞ Scene 2    
          "Chill Scene" = {
              "PC" = { state = "ON"; brightness = 200; color = { hex = "#8A2BE2"; }; };               # 🦆 says ⮞ Blue Violet
              "Golvet" = { state = "ON"; brightness = 200; color = { hex = "#40E0D0"; }; };           # 🦆 says ⮞ Turquoise
              "Uppe" = { state = "ON"; brightness = 200; color = { hex = "#FF69B4"; }; };             # 🦆 says ⮞ Hot Pink
              "Spotlight kök 1" = { state = "OFF"; brightness = 200; color = { hex = "#FFD700"; }; }; # 🦆 says ⮞ Gold
              "Spotlight Kök 2" = { state = "OFF"; brightness = 200; color = { hex = "#FF8C00"; }; }; # 🦆 says ⮞ Dark Orange
              "Taket Sovrum 1" = { state = "ON"; brightness = 200; color = { hex = "#00CED1"; }; };   # 🦆 says ⮞ Dark Turquoise
              "Taket Sovrum 2" = { state = "ON"; brightness = 200; color = { hex = "#9932CC"; }; };   # 🦆 says ⮞ Dark Orchid
              "Bloom" = { state = "ON"; brightness = 200; color = { hex = "#FFB6C1"; }; };            # 🦆 says ⮞ Light Pink
              "Sänggavel" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine
              "Takkrona 1" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
              "Takkrona 2" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
              "Takkrona 3" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
              "Takkrona 4" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
          }; 
          "Green D" = {
              "PC" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Golvet" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Uppe" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Spotlight kök 1" = { state = "OFF"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Spotlight Kök 2" = { state = "OFF"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Taket Sovrum 1" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Taket Sovrum 2" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Bloom" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Sänggavel" = { state = "ON"; brightness = 200; color = { hex = "#00FF00"; }; };
              "Takkrona 1" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
              "Takkrona 2" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
              "Takkrona 3" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
              "Takkrona 4" = { state = "ON"; brightness = 200; color = { hex = "#7FFFD4"; }; };        # 🦆 says ⮞ Aquamarine   
          };  
          "dark" = { # 🦆 says ⮞ eat darkness... lol YO! You're as blind as me now! HA HA!  
              "Bloom" = { state = "OFF"; transition = 10; };
              "Dörr" = { state = "OFF"; transition = 10; };
              "Golvet" = { state = "OFF"; transition = 10; };
              "Kök Bänk Slinga" = { state = "OFF"; transition = 10; };
              "PC" = { state = "OFF"; transition = 10; };
              "Rustning" = { state = "OFF"; transition = 10; };
              "Spotlight Kök 2" = { state = "OFF"; transition = 10; };
              "Spotlight kök 1" = { state = "OFF"; transition = 10; };
              "Sänggavel" = { state = "OFF"; transition = 10; };
              "Sänglampa" = { state = "OFF"; transition = 10; };
              "Tak Hall" = { state = "OFF"; transition = 10; };
              "Taket Sovrum 1" = { state = "OFF"; transition = 10; };
              "Taket Sovrum 2" = { state = "OFF"; transition = 10; };
              "Uppe" = { state = "OFF"; transition = 10; };
              "Vägg" = { state = "OFF"; transition = 10; };
              "WC 1" = { state = "OFF"; transition = 10; };
              "WC 2" = { state = "OFF"; transition = 10; };
              "Takkrona 1" = { state = "OFF"; transition = 10; };   
              "Takkrona 2" = { state = "OFF"; transition = 10; };
              "Takkrona 3" = { state = "OFF"; transition = 10; };   
              "Takkrona 4" = { state = "OFF"; transition = 10; };   
          };  
          "dark-fast" = { # 🦆 says ⮞ eat darkness... NAO!  
              "Bloom" = { state = "OFF"; };
              "Dörr" = { state = "OFF"; };
              "Golvet" = { state = "OFF"; };
              "Kök Bänk Slinga" = { state = "OFF"; };
              "PC" = { state = "OFF"; };
              "Rustning" = { state = "OFF"; };
              "Spotlight Kök 2" = { state = "OFF"; };
              "Spotlight kök 1" = { state = "OFF"; };
              "Sänggavel" = { state = "OFF"; };
              "Sänglampa" = { state = "OFF"; };
              "Tak Hall" = { state = "OFF"; };
              "Taket Sovrum 1" = { state = "OFF"; };
              "Taket Sovrum 2" = { state = "OFF"; };
              "Uppe" = { state = "OFF"; };
              "Vägg" = { state = "OFF"; };
              "WC 1" = { state = "OFF"; };
              "WC 2" = { state = "OFF"; };
              "Takkrona 1" = { state = "OFF"; };   
              "Takkrona 2" = { state = "OFF"; };
              "Takkrona 3" = { state = "OFF"; };   
              "Takkrona 4" = { state = "OFF"; };   
          };  
          "max" = { # 🦆 says ⮞ let there be light
              "Bloom" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Dörr" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Golvet" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Kök Bänk Slinga" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "PC" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Rustning" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Spotlight Kök 2" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Spotlight kök 1" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Sänggavel" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Sänglampa" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Tak Hall" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Taket Sovrum 1" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Taket Sovrum 2" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Uppe" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Vägg" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "WC 1" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "WC 2" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Takkrona 1" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };   
              "Takkrona 2" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
              "Takkrona 3" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };   
              "Takkrona 4" = { state = "ON"; brightness = 255; color = { hex = "#FFFFFF"; }; };
          };    
      };       
    };
    
    # 🦆 ⮞ TV ⮜
    # 🦆says⮞ configure TV devices with: room, ip, apps & channel information
    tv = {
      # 🦆 says ⮞ Livingroom
      shield = {
        enable = true;
        room = "livingroom";
        ip = "192.168.1.223";
        apps = {
          telenor = "se.telenor.stream/.MainActivity";
          tv4 = "se.tv4.tv4playtab/se.tv4.tv4play.ui.mobile.main.BottomNavigationActivity";
        };  
        channels = {     
          "1" = {
            name = "SVT1";
            id = 1; # 🦆 says ⮞ adb channel ID
            # 🦆 says ⮞ OR
            # stream_url = "https://url.com/";
            cmd = "open_telenor && wait 5 && start_channel_1";
            # 🦆 says ⮞ automagi generated tv-guide web & EPG          
            icon = ./themes/icons/tv/1.png;
            scrape_url = "https://tv-tabla.se/tabla/svt1/";          
          };
          "2" = {
            id = 2; 
            name = "SVT2";
            cmd = "open_telenor && wait 5 && start_channel_2";
            icon = ./themes/icons/tv/2.png;          
            scrape_url = "https://tv-tabla.se/tabla/svt2/";
          };
          "3" = {
            id = 3;
            name = "Kanal 3";
            cmd = "open_telenor && wait 5 && start_channel_3";
            icon = ./themes/icons/tv/3.png;
            scrape_url = "https://tv-tabla.se/tabla/tv3/";
          };
          "4" = {
            id = 4;
            name = "TV4";
            cmd = "open_telenor && wait 5 && start_channel_4";
            icon = ./themes/icons/tv/4.png;
            scrape_url = "https://tv-tabla.se/tabla/tv4/";
          };
          "5" = {
            id = 5;
            name = "Kanal 5";
            cmd = "open_telenor && wait 5 && start_channel_5";
            icon = ./themes/icons/tv/5.png;
            scrape_url = "https://tv-tabla.se/tabla/kanal_5/";
          };
          "6" = {
            id = 6;
            name = "Kanal 6";
            cmd = "open_telenor && wait 5 && start_channel_6";
            icon = ./themes/icons/tv/6.png;
            scrape_url = "https://tv-tabla.se/tabla/tv6/";
          };
          "7" = {
            id = 7;
            name = "Sjuan";
            cmd = "open_telenor && wait 5 && start_channel_7";
            icon = ./themes/icons/tv/7.png;
            scrape_url = "https://tv-tabla.se/tabla/sjuan/";
          };
          "8" = {
            id = 8;
            name = "TV8";
            icon = ./themes/icons/tv/8.png;          
            scrape_url = "https://tv-tabla.se/tabla/tv8/";
          };
          "9" = {
            id = 9;
            name = "Kanal 9";
            icon = ./themes/icons/tv/9.png;          
            scrape_url = "https://tv-tabla.se/tabla/kanal_9/";
          };
          "10" = {
            id = 10;
            name = "Kanal 10";
            icon = ./themes/icons/tv/10.png;
            scrape_url = "https://tv-tabla.se/tabla/tv10/";
          };
          "11" = {
            id = 11;
            name = "Kanal 11";
            icon = ./themes/icons/tv/11.png;
            scrape_url = "https://tv-tabla.se/tabla/tv11/";
          };
          "12" = {
            id = 12;
            name = "Kanal 12";
            icon = ./themes/icons/tv/12.png;
            scrape_url = "https://tv-tabla.se/tabla/tv12/";
          };
          "13" = {
            id = 13;
            name = "TV4 Hockey";
            icon = ./themes/icons/tv/13.png;
            cmd = "open_tv4 && nav_select && nav_left && nav_down && nav_doown && nav_down && nav_select && wait 3 && nav_down && nav_down && nav_down && nav_down && nav_down && nav_select";
            scrape_url = "https://tv-tabla.se/tabla/tv4_hockey/";
          };        
          "14" = {
            id = 14;
            name = "TV4 Sport Live 1";
            icon = ./themes/icons/tv/14.png;
            cmd = "open_tv4 && nav_left && nav_down && nav_down && nav_down && nav_select && wait 3 && nav_down && nav_down && nav_down && nav_down && nav_down && nav_right && nav_right && nav_select";
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_1/";
          };
          "15" = {
            id = 15;
            name = "TV4 Sport Live 2";
            icon = ./themes/icons/tv/15.png;
            cmd = "open_tv4 && nav_select && nav_left && nav_down && nav_down && nav_down && nav_select && wait 3 && nav_down && nav_down && nav_down && nav_down && nav_down && nav_down && nav_select";    
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_2/";
          };
          "16" = {
            id = 16;
            name = "TV4 Sport Live 3";
            icon = ./themes/icons/tv/16.png;
            cmd = "open_tv4 && nav_down && nav_right && nav_right && nav_center";
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_3/";
          };
          "17" = {
            id = 17;
            name = "TV4 Sport Live 4";
            icon = ./themes/icons/tv/17.png;
            cmd = "open_tv4 && nav_left && nav_down && nav_down && nav_down && nav_select && wait 3 && nav_down && nav_down && nav_down && nav_down && nav_down && nav_down && nav_right && nav_right && nav_select";
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_4/";
          };       
        };
      };
      # 🦆 says ⮞ Bedroom
      arris = {
        enable = true;
        room = "bedroom";
        ip = "192.168.1.152"; 
        apps = {
          telenor = "se.telenor.stream/.MainActivity   ";
          tv4 = "se.tv4.tv4playtab/se.tv4.tv4play.ui.mobile.main.BottomNavigationActivity";
        };
        channels = {     
          "1" = {
            id = 1;
            name = "SVT1";
            icon = ./themes/icons/tv/1.png;
            scrape_url = "https://tv-tabla.se/tabla/svt1/";
          };
          "2" = {
            id = 2; 
            name = "SVT2";
            icon = ./themes/icons/tv/2.png;
            scrape_url = "https://tv-tabla.se/tabla/svt2/";
          };
          "3" = {
            id = 3;
            name = "Kanal 3";
            icon = ./themes/icons/tv/3.png;
            scrape_url = "https://tv-tabla.se/tabla/tv3/";
          };
          "4" = {
            id = 4;
            name = "TV4";
            icon = ./themes/icons/tv/4.png;
            scrape_url = "https://tv-tabla.se/tabla/tv4/";
          };
          "5" = {
            id = 5;
            name = "TV5";
            icon = ./themes/icons/tv/5.png;
            scrape_url = "https://tv-tabla.se/tabla/kanal_5/";
          };
          "6" = {
            id = 6;
            name = "Kanal 6";
            icon = ./themes/icons/tv/6.png;
            scrape_url = "https://tv-tabla.se/tabla/tv6/";
          };
          "7" = {
            id = 7;
            name = "Sjuan";
            icon = ./themes/icons/tv/7.png;
            scrape_url = "https://tv-tabla.se/tabla/sjuan/";
          };
          "8" = {
            id = 8;
            name = "TV8";
            icon = ./themes/icons/tv/8.png;          
            scrape_url = "https://tv-tabla.se/tabla/tv8/";
          };
          "9" = {
            id = 9;
            name = "Kanal 9";
            icon = ./themes/icons/tv/9.png;          
            scrape_url = "https://tv-tabla.se/tabla/kanal_9/";
          };
          "10" = {
            id = 10;
            name = "Kanal 10";
            icon = ./themes/icons/tv/10.png;
            scrape_url = "https://tv-tabla.se/tabla/tv10/";
          };
          "11" = {
            id = 11;
            name = "Kanal 11";
            icon = ./themes/icons/tv/11.png;
            scrape_url = "https://tv-tabla.se/tabla/tv11/";
          };
          "12" = {
            id = 12;
            name = "Kanal 12";
            icon = ./themes/icons/tv/12.png;
            scrape_url = "https://tv-tabla.se/tabla/tv12/";
          };
          "13" = {
            id = 13;
            name = "TV4 Hockey";
            icon = ./themes/icons/tv/13.png;
            cmd = "nav_down && nav_down && nav_right && nav_right && nav_center";          
            scrape_url = "https://tv-tabla.se/tabla/tv4_hockey/";
          };        
          "14" = {
            id = 14;
            name = "TV4 Sport Live 1";
            icon = ./themes/icons/tv/14.png;
            cmd = "nav_down && nav_down && nav_right && nav_right && nav_center";     
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_1/";
          };
          "15" = {
            id = 15;
            name = "TV4 Sport Live 2";
            icon = ./themes/icons/tv/15.png;
            cmd = "nav_down && nav_down && nav_right && nav_right && nav_center";      
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_2/";
          };
          "16" = {
            id = 16;
            name = "TV4 Sport Live 3";
            icon = ./themes/icons/tv/16.png;
            cmd = "nav_down && nav_down && nav_right && nav_right && nav_center";      
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_3/";
          };
          "17" = {
            id = 17;
            name = "TV 4 Sport Live 4";
            icon = ./themes/icons/tv/17.png;
            cmd = "nav_down && nav_down && nav_right && nav_right && nav_center";
            scrape_url = "https://tv-tabla.se/tabla/tv4_sport_live_4/";
          };       
        };
      };
    };

  };}
And you'll get a dashboard for your devices generated and found at http://localhost:13337

Watch demo


The dashboard currently gives you:

  • Advanced zigbee device control
  • Remote for your Android TV devices
  • Set scenes
  • Access to yo do through both an text input field aswell as microphone
  • and more...
I like my flakes tiny & ny modules dynamically loaded,
# dotfiles/flake.nix ⮞ https://github.com/quackhack-mcblindy/dotfiles
{
    description = "❄️🦆 ⮞ QuackHack-McBLindy's NixOS flake";
    inputs = {
        nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";        
        sops-nix.url = "github:Mic92/sops-nix";
        sops-nix.inputs.nixpkgs.follows = "nixpkgs";  
        caddy-duckdns.url = "github:QuackHack-McBlindy/nix-caddy-duckdns";
        installer.url = "github:QuackHack-McBlindy/auto-installer-nixos";
    };
    outputs = inputs @ { self, systems, nixpkgs, ... }:
        let
            lib = import ./lib { 
                inherit self inputs;
                lib = nixpkgs.lib;      
            };                   
        in lib.makeFlake {
            systems = [ "x86_64-linux" "aarch64-linux" ]; 
            overlays = lib.mapOverlays ./overlays { inherit lib; };
            hosts = lib.mapHosts ./hosts;
            specialArgs = { pkgs = system: nixpkgs.legacyPackages.${system}; };
            packages = lib.mapModules ./packages import;
            devShells = lib.mapModules ./devShells (path: import path);     
        };} # 🦆 duck say ⮞ flakes all set, with no debating — next nix file awaiting, ducks be there waitin'

View Flake Outputs
git+file:///home/pungkula/dotfiles?ref=refs/heads/main&rev=8901ec868ee3dbd16c532980f68227a20cbc63b7
├───devShells
│   ├───aarch64-linux
│   │   ├───android omitted (use '--all-systems' to show)
│   │   ├───esphome omitted (use '--all-systems' to show)
│   │   ├───go omitted (use '--all-systems' to show)
│   │   ├───java omitted (use '--all-systems' to show)
│   │   ├───node omitted (use '--all-systems' to show)
│   │   ├───python312 omitted (use '--all-systems' to show)
│   │   ├───python38 omitted (use '--all-systems' to show)
│   │   └───rust omitted (use '--all-systems' to show)
│   └───x86_64-linux
│       ├───android: development environment 'nix-shell'
│       ├───esphome: development environment 'nix-shell'
│       ├───go: development environment 'nix-shell'
│       ├───java: development environment 'nix-shell'
│       ├───node: development environment 'nix-shell'
│       ├───python312: development environment 'nix-shell'
│       ├───python38: development environment 'nix-shell'
│       └───rust: development environment 'nix-shell'
├───nixosConfigurations
│   ├───desktop: NixOS configuration
│   ├───homie: NixOS configuration
│   ├───laptop: NixOS configuration
│   └───nasty: NixOS configuration
├───overlays
│   └───noisereduce: Nixpkgs overlay
└───packages
    ├───aarch64-linux
    │   ├───health omitted (use '--all-systems' to show)
    │   ├───installer omitted (use '--all-systems' to show)
    │   ├───jellyfin omitted (use '--all-systems' to show)
    │   ├───say omitted (use '--all-systems' to show)
    │   ├───tv omitted (use '--all-systems' to show)
    │   └───yo-bitch omitted (use '--all-systems' to show)
    └───x86_64-linux
        ├───health: package 'health'
        ├───installer: package 'nixos-auto-installer-24.05.20240406.ff0dbd9-x86_64-linux.iso'
        ├───jellyfin: package 'jellyfin'
        ├───say: package 'say'
        ├───tv: package 'tv'
        └───yo-bitch: package 'yo-bitch'

🛟 Quick Start

Build automated, offline USB NixOS installer

$ sudo bash usb-installer \
  --user "pungkula" \
  --host "laptop" \
  --ssid "IfYouDontHaveEthernet" \
  --wifipass "CanBeOmitted" \
  --publickey "ssh-ed25519 AAAAC3FoRSsHCoNnEcTiOn..."

# dd result to flash drive (replace sdX)
$ sudo dd if="$(readlink -f ./result/iso/*.iso)" of=/dev/sdX bs=4M status=progress oflag=sync

Plug in flash drive into laptop and boot. Let it work and wait until it powers down.
Remove flash drive, boot it up again and deploy configuration from your main machine:

# 🦆🔓 First deploy? Get your Yubikey: PIN+Touch unlocks host specific AGE key for sops-nix 
$ yo deploy laptop

Any builds after first deployment will use local cached binaries for enhanced build time.


🚀 yo CLI 🦆

The yo CLI is a framework designed to execute scripts defined in the ./bin directory.
It provides a unified interface for script execution, centralizes all help commands, and automatically validates parametrs and updates the documentation.

Usage: yo <command> [arguments]

Usage Examples:

The yo CLI supports flexible parameter parsing through two primary mechanisms:

# Named Parameters  
$ yo deploy --host laptop --flake /home/pungkula/dotfiles

# Positional Parameters
$ yo deploy laptop /home/pungkula/dotfiles

# Scripts can also be executed with natural language text by typing:
$ yo do "is laptop overheating"
# Natural language voice commands are also supported, say:
"yo bitch reboot the laptop"

# If the server is not running, it can be manually started with:
$ yo transcribe
$ yo wake

✨ Available Commands

Set default values for your parameters to have them marked [optional]

Command Syntax Aliases Description VoiceReady
🖥️ System Management
yo deploy --host [--flake] [--user] [--repo] [--port] [--test] Build and deploy a NixOS configuration to a remote host. Bootstraps, builds locally, activates remotely, and auto-tags the generation.
yo dev [--devShell] Start development enviorment 📛
yo dry Build and deploy a NixOS configuration to a remote host. Bootstraps, builds locally, activates remotely, and auto-tags the generation. 📛
yo esp [--device] [--serialPort] [--ota] [--otaPort] [--OTAPwFile] [--wifiSSID] [--wifiPwFile] [--mqttHost] [--mqttUser] [--mqttPwFile] [--transcriptionHostIP] Declarative firmware deployment tool for ESP32 boards with built-in version control. 📛
yo espOTA Updates ESP32 devices over the air. 📛
yo reboot [--host] restart Force reboot and wait for host
yo rollback --host [--flake] [--user] Rollback a host to a previous NixOS generation. Fetches Git tags and reverts system+config to a synced, tagged state. 📛
yo services --operation --service --host [--user] [--port] [--!] Systemd service handler.
yo switch [--flake] [--!] rb Rebuild and switch Nix OS system configuration. ('!' to test)
⚡ Productivity
yo calculator --expression calc Calculate math expressions
yo calendar [--operation] [--calenders] kal Calendar assistant. Provides easy calendar access. Interactive terminal calendar, or manage the calendar through yo commands or with voice.
yo clip2phone --copy Send clipboard to an iPhone, for quick copy paste
yo fzf f Interactive fzf search for file content with quick edit & jump to line 📛
yo google --search [--apiKeyFile] [--searchIDFile] g Perform web search on google
yo hitta --search Locate a persons address with help of Hitta.se
yo img2phone --image Send images to an iPhone 📛
yo pull [--flake] [--host] Pull the latest changes from your dotfiles repo. Resets tracked files to origin/main but keeps local extras.
yo push [--flake] [--repo] [--host] [--generation] ps Commit, tag, and push dotfiles and system state to GitHub. Tags based on host + generation, auto-updates README, and preserves history. 📛
yo scp --host [--path] [--username] [--downloadPath] Move files between hosts interactively 📛
yo update-readme [--readmePath] Updates the documentation in README.md 📛
🌍 Localization
yo stores --store_name [--location] [--radius] store, shop Finds nearby stores using OpenStreetMap data with fuzzy name matching. Returns results with opening hours.
yo travel [--arrival] [--departure] [--type] [--apikeyPath] Public transportation helper. Fetches current bus and train schedules. (Sweden)
yo weather [--location] [--day] [--condition] [--locationPath] weat Weather Assistant. Ask anything weather related (3 day forecast)
🌐 Networking
yo api [--host] [--port] [--dir] Simple API for collecting system data 📛
yo block --url [--blocklist] ad Block URLs using DNS 📛
yo ip-updater [--token1] [--token2] [--token3] DDNS updater
yo notify [--text] [--title] [--icon] [--url] [--group] [--sound] [--volume] [--copy] [--autoCopy] [--level] [--encrypt] [--base_urlFile] [--deviceKeyFile] Send custom push to iOS devices 📛
yo notify-me [--address] [--port] [--dataDir] Notification server for iOS devices 📛
yo shareWiFi [--ssidFile] [--passwordFile] creates a QR code of guest WiFi and push image to iPhone
yo speed st Test internet download speed
🎧 Media Management
yo call-remote call Used to call the tv remote, for easy localization.
yo hacker-news [--show] [--item] [--user] [--clear] [--number] hn Hacker news API controller 📛
yo news [--apis] [--clear] [--playedFile] API caller and playlist manager for latest Swedish news from SR.
yo transcode [--directory] trans Transcode media files 📛
yo tv [--typ] [--search] [--device] [--season] [--shuffle] [--tvshowsDir] [--moviesDir] [--musicDir] [--musicvideoDir] [--videosDir] [--podcastDir] [--audiobookDir] [--youtubeAPIkeyFile] [--webserver] [--defaultPlaylist] [--favoritesPlaylist] [--max_items] [--mqttUser] [--mqttPWFile] remote Android TV Controller. Fuzzy search all media types and creates playlist and serves over webserver for casting. Fully conttrollable.
yo tv-guide [--search] [--channel] [--jsonFilePath] tvg TV-guide assistant..
yo tv-scraper [--epgFilePath] [--jsonFilePath] [--flake] tvs Scrapes web for tv-listing data. Builds EPG and generates HTML. 📛
yo vlc [--add] [--addDir] [--remove] [--list] [--shuffle] [--playlist] Playlist management for the local machine 📛
📁 File Operations
yo copy --from --to cp Copy a file or directory to a new location
yo list [--path] ls List directory contents with details
yo makedir --path mkd Create a new directory with parents if needed
yo move --from --to mv Move a file or directory to a new location
yo nano --file --content Write content to filepath
yo remove --target rm, delete Remove files or directories safely
🔐 Security & Encryption
yo sops --input [--operation] [--value] [--output] [--agePub] e Encrypts a file with sops-nix 📛
yo yubi --operation --input yk Encrypts and decrypts files using a Yubikey and AGE 📛
🗣️ Voice
yo cancel [--input] Cancel coammands microphone recording sent to transcription.
yo do [--input] [--fuzzy] [--dir] [--build] [--realtime] brain Brain (do) is a Natural Language to Shell script translator that generates dynamic regex patterns at build time for defined yo.script sentences. At runtime it runs exact and fuzzy pattern matching with automatic parameter resolution and seamless execution 📛
yo do-bash --input [--fuzzy] [🦆🧠] yo do - The Brain of this repository. Natural language to Shell script translator with dynamic regex matching and automatic parameter resolutiion with some fuzzy on top of that. Written in Bash (slower, but more dope🦆, don't ya think?) 📛
yo espaudio WIP! ESP32 audio development 📛
yo kill-mic Kill mic-stream by port with voice
yo memory [--show] [--record] [--good] [--tail] [--reset] stats Memory is stats and metrics that acts as contexual awareness for the natural langugage processor. 📛
yo mic [--port] [--host] [--seconds] [🦆🎙️] Trigger microphone recording sent to transcription. 📛
yo mic-stream [--chunk] [--silence] [--silenceLevel] Stream microphone audio to WS chunk transcription 📛
yo say --text [--model] [--modelDir] [--silence] [--host] [--blocking] [--file] [--caf] Text to speech with built in language detection and automatic model downloading
yo sleep --time Waits for specified time (seconds). Useful in command chains.
yo tests [--input] [--stats] Extensive automated sentence testing for the NLP
yo tests-rs [--input] [--stats] [--fuzzy] [--dir] [--build] [--realtime] Extensive automated sentence testing for the NLP () 📛
yo train --phrase Trains the NLP module. Correct misclassified commands and update NLP patterns 📛
yo transcribe [--port] [--model] [--language] [--beamSize] [--gpu] [--cert] [--key] Transcription server-side service. Sit and waits for audio that get transcribed and returned. 📛
yo transcription-ws WebSocket server for real-time transcription streaming to NLP 📛
yo wake [--threshold] [--cooldown] [--sound] [--remoteSound] [--redisHost] [--redis_pwFIle] Run Wake word detection for audio recording and transcription 📛
🛖 Home Automation
yo alarm --hours --minutes [--list] [--sound] wakeup Set an alarm for a specified time
yo battery [--device] Fetch battery level for specified device.
yo bed [--part] [--state] Bed controller
yo blinds [--state] Turn blinds up/down
yo chair [--part] [--state] Chair controller
yo duckDash [--host] [--port] [--cert] [--key] dash Mobile-first dashboard, unified frontend for Zigbee devices, tv remotes, and other smart home gadgets. Includes DuckCloud page for easy access to your files. (Use WireGuard) 📛
yo findPhone Helper for locating Phone
yo house [--device] [--state] [--brightness] [--color] [--temperature] [--scene] [--room] [--user] [--passwordfile] [--flake] [--pair] [--cheapMode] Control lights and other home automatioon devices
yo kitchenFan [--state] Turns kitchen fan on/off
yo leaving Run when leaving house to set away state 📛
yo returned Run when returned home to set home state 📛
yo robobot --device [--mode] [--state] [--delay] [--reverse] [--lower] [--upper] [--touch] [--user] [--passwordfile] Designed to simplify configuring the Zigbee Fingerbot Plus 📛
yo state [--device] Fetches the state of the specified device.
yo temperatures Get all temperature values from sensors and return a average value.
yo tibber [--mode] [--homeIDFile] [--APIKeyFile] [--filePath] [--user] [--pwfile] el Fetches home electricity price data
yo timer [--minutes] [--seconds] [--hours] [--list] [--sound] Set a timer
yo toilet Flush the toilet
yo zigduck [--user] [--pwfile] [🦆🏡] yo zigduck - Home automation system written in Bash 📛
yo zigduck-rs [--dir] [--user] [--pwfile] [🦆🏡] ZigDuck - Home automation system! Devices, scenes, automations -- EVERYTHING is defined using Nix options from the module 'house.nix'. (Written in Rust) 📛
🧩 Miscellaneous
yo btc [--filePath] [--user] [--pwfile] Crypto currency BTC price tracker
yo chat --text No fwendz? Let's chat yo!
yo duckPUCK [--mode] [--team] [--stat] [--count] [--dataDir] puck [🏒🦆] - Your Personal Hockey Assistant! - Expert commentary and analyzer specialized on Hockey Allsvenskan (SWE). Analyzing games, scraping scoreboards and keeping track of all dates annd numbers.
yo hockeyGames [--type] [--days] [--team] [--dataDir] [--debug] hag Hockey Assistant. Provides Hockey Allsvenskan data and deliver analyzed natural language responses (TTS).
yo invokeai --prompt [--host] [--port] [--outputDir] [--width] [--height] [--steps] [--cfgScale] [--seed] [--model] genimg AI generated images powered by InvokeAI
yo joke [--jokeFile] Duck says s funny joke.
yo post [--postalCodeFile] [--postalCode] Check for the next postal delivery day. (Sweden)
yo qr --input [--icon] [--output] Create fun randomized QR codes from input. 📛
yo reminder [--about] [--list] [--clear] [--user] [--pwfile] remind Reminder Assistant
yo shop-list [--operation] [--item] [--list] [--mqttUser] [--mqttPWFile] Shopping list management
yo suno --prompt [--genre] mg AI generated lyrics and music files powered by Suno
yo time Tells time, day and date
yo xmr [--filePath] [--user] [--pwfile] Crypto currency XMR price tracker
🧹 Maintenance
yo clean gc Run a total garbage collection: Removes old NixOS generations, empty trash, flush tmp files, whipes cache and runs a docker prune 📛
yo duckTrace [--script] [--host] [--errors] [--monitor] log View duckTrace logs quick and quack, unified logging system
yo health hc Check system health status across your machines. Returns JSON structured responses.

❓ Detailed Help

For specific command help: yo <command> --help yo <command> -h


💬 Comments?

Nix Talk? Or just say tiny flake sucks?
That's cool!
I am all ears. 👀


Discord Email GitHub Discussions


🦆 Follow my Adventures

𝑸𝓾𝒂𝒄𝒌𝑯𝒂𝒄𝒌-𝑴𝒄𝑩𝒍𝒊𝒏𝒅𝒚 𝗕𝗹𝗼𝗴


Note

Im not blind.
I just can't see. 🧑‍🦯


About

It’s not “just dotfiles” - it's extreme modularity that looks and feels like a NixOS flake. It's Voice-driven DevOps, it's a declarative home automation system, it's a self-contained auto-documenting monster.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published