Compare commits

...

No commits in common. "master" and "before-git" have entirely different histories.

488 changed files with 358 additions and 32908 deletions

View file

@ -1,19 +0,0 @@
name: Discord
on:
- workflow_dispatch
- push
jobs:
discord_commits:
runs-on: ubuntu-latest
name: discord commits
if: contains(github.event.head_commit.message, '(servers)')
steps:
- name: Discommit
uses: https://github.com/matt1432/discommit@v0.0.2
with:
discord_webhook: ${{ secrets.DISCORD_WEBHOOK }}
api_url: 'https://git.nelim.org/api/v1/repos/$OWNER/$REPO/git/commits/$REF'
title: 'New commit containing changes to server configs:'

5
.gitattributes vendored
View file

@ -1,5 +0,0 @@
flake.lock -diff
flake.nix -diff
**/non-declarative-conf -diff
**/package-lock.json -diff
**/HomeAssistantGenerated -diff

23
.gitignore vendored
View file

@ -1,23 +0,0 @@
# Python
*.egg-info
# NPM
*node_modules
*build/
# Direnv
*.direnv/
# Generated by nix
result*
!results/
.nixd.json
## AGS
**/vars.ts
**/config.js
*icons
**/types
# Other
*.temp

View file

@ -1,16 +0,0 @@
MIT No Attribution
Copyright 2024 Mathis H.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

340
README.md
View file

@ -1,120 +1,236 @@
# My NixOS configs # Archinstaller
```
TODO: add directory structure info and enforce it loadkeys ca
- every root folder in the repo represents a flake output except inputs setfont ter-132b
- every root folder only has a `default.nix` and subfolders for each
of its attrs
- in a subfolder, there should always be a `default.nix`
- if there is non nix code, it will be in a `config` folder
- redo docs
- every module should not do anything if imported
## AGS
You might find it weird that most of my config is written in TypeScript.
That's because all my desktops run
[AGS](https://github.com/Aylur/ags)
for UI. Click on
[this](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/modules/ags)
to see my configuration.
I'm also a victim of Stockholm syndrome at this point and make my scripts
in TypeScript because it's the scripting language I am most comfortable with.
## About
### General
This repo is the complete configuration of machines I own,
running NixOS or Nix. Its structure is based on a flake's
[outputs](https://wiki.nixos.org/wiki/Flakes#Output_schema).
### Flake Location
This git repo will always be located at `$FLAKE` (`config.environment.variables.FLAKE`)
and symlinked to `/etc/nixos` to have everything where NixOS tools
expect things to be.
ie.
```bash
sudo rm -r /etc/nixos
echo "$FLAKE" # /home/matt/.nix
sudo ln -sf /home/matt/.nix /etc/nixos
``` ```
### Flake Outputs ## Partionning with [cryptsetup](https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#LUKS_on_a_partition)
### Encrypting root partition
| Output | Description | ```
| ---------------------------------- | ----------- | $ PART="encrypted partition number ie. 2"
| `nixosConfigurations` | [devices](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/devices)' + ISO's configurations | $ cryptsetup -y -v luksFormat --type luks1 /dev/nvme0n1p$PART
| `nixOnDroidConfigurations.default` | [Nix-On-Droid](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/devices/android)'s configuration | $ cryptsetup open /dev/nvme0n1p$PART root
| `packages` | Some custom [packages](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/packages) not available in nixpkgs or modified from it | $ mkfs.btrfs /dev/mapper/root
| `scopedPackages` | Some custom [package scopes](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/scopedPackages) not available in nixpkgs or modified from it | $ mount /dev/mapper/root /mnt
| `apps` | Scripts ran from the flake defined [here](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/apps) | ```
| `homeManagerModules` | [Modules](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/homeManagerModules) made for home-manager | ### Mounting boot :
| `homeManagerModules` | [Modules](https://git.nelim.org/matt1432/nixos-configs/src/branch/master/modules) made for NixOS systems | ```
| `formatter` | I format nix code with [alejandra](https://github.com/kamadorueda/alejandra) | $ mount --mkdir /dev/nvme0n1p1 /mnt/boot
| `devShells.default` | A dev shell to build an ISO from the live-image nixosConfiguration |
| `devShells.ags` | A dev shell to have a NodeJS env when I enter my AGS's config directory |
### Flake Inputs
To allow use of the full nix language for my inputs, I use [genflake](https://github.com/jorsn/flakegen).
Therefore, the flake I edit is located at `./outputs.nix`.
I also prefer using a more descriptive format for my inputs like so:
```nix
nixpkgs = {
type = "github";
owner = "NixOS";
repo = "nixpkgs";
# Branch name
ref = "nixos-unstable";
# Pin this input to a specific commit
rev = "842d9d80cfd4560648c785f8a4e6f3b096790e19";
};
``` ```
to make it more clear what is what in the flake URI ### Installing packages on the device
```
### Secrets $ pacstrap -K /mnt base linux-firmware linux amd-ucode patch dkms kmod btrfs-progs grub os-prober ntfs-3g efibootmgr efivar networkmanager iwd nano sudo texinfo man-db man-pages
All my secrets are in a private git repo that makes use of
[sops-nix](https://github.com/Mic92/sops-nix).
I generate `.sops.yaml` from `.sops.nix`:
```nix
let
wim = "somekey";
binto = "somekey2";
in {
creation_rules = [
{
path_regex = "secrets/[^/]+\\.(yaml|json|env|ini)$";
key_groups = [
{
age = [wim binto];
}
];
}
];
}
``` ```
which is then converted to `.sops.yaml` using ## Preparing for chroot
[remarshal](https://github.com/remarshal-project/remarshal) ```
and this shell command: $ genfstab -U /mnt >> /mnt/etc/fstab
$ arch-chroot /mnt
```bash
nix eval --json --file ./.sops.nix | remarshal --if json --of yaml > .sops.yaml
``` ```
TLDR: I # Chroot in Installed Arch
**[hate](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell)** ```
YAML $ ln -sf /usr/share/zoneinfo/America/Montreal /etc/localtime
$ hwclock --systohc
$ echo matt-laptop > /etc/hostname
$ passwd
```
## Localization
Uncomment ca_FR.UTF-8 en_CA.UTF-8 en_US.UTF-8 fr_CA.UTF-8 and run
```
$ locale-gen
$ echo LANG=en_CA.UTF-8 > /etc/locale.conf
$ echo KEYMAP=ca > /etc/vconsole.conf
```
## Edit /etc/mkinitcpio.conf for LUKS
```
$ sed -i 's/BINARIES=.*/BINARIES=(btrfs)/' /etc/mkinitcpio.conf
...
$ sed -i 's/HOOKS=.*/HOOKS=(base udev autodetect modconf kms keyboard keymap consolefont block encrypt filesystems fsck)/' /etc/mkinitcpio.conf
```
then run ```mkinitpcio -P```
## Grub install
```
$ grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=wim --boot-directory=/boot/EFI/wim
```
### Edit /etc/default/grub for LUKS
```
$ CRYPT="cryptdevice=$(blkid | sed -n 's/.*nvme0n1p'$PART': \(.*\) TYPE.*/\1/p'):root"
$ sed -i 's#GRUB_CMDLINE_LINUX_DEFAULT.*#GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel 3 '$CRYPT' root=/dev/mapper/root"#' /etc/default/grub
```
make sure the UUID is the actual partition inside the LUKS container and run ```grub-mkconfig -o /boot/EFI/wim/grub/grub.cfg```
we can now reboot to the installed Arch
<br/><br/>
# Inside installed Arch
## Configure [internet](https://wiki.archlinux.org/title/Iwd) access
```
$ systemctl enable --now NetworkManager systemd-networkd systemd-resolved systemd-timesyncd
$ cat << EOF >> /etc/NetworkManager/conf.d/wifi_backend.conf
[device]
wifi.backend=iwd
EOF
$ cat << EOF >> /etc/iwd/main.conf
[General]
EnableNetworkConfiguration=true
EOF
$ systemctl stop iwd
$ systemctl restart NetworkManager
$ iwctl device list # check if powered on
$ iwctl station wlan0 scan
$ iwctl station wlan0 get-networks
$ iwctl station wlan0 connect $SSID
```
### Configure systemd-resolved
```
$ ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
$ sed -i 's/#DNS=.*/DNS=100.64.0.1/' /etc/systemd/resolved.conf
$ sed -i 's/#FallbackDNS=.*/FallbackDNS=1.1.1.1/' /etc/systemd/resolved.conf
$ sed -i 's/#DNSStubListener=.*/DNSStubListener=no/' /etc/systemd/resolved.conf
```
### Configure reflector for mirror management of pacman
```
$ pacman -Sy reflector
$ nano /etc/xdg/reflector/reflector.conf
$ systemctl enable --now reflector.timer
```
## User management
```
$ useradd -m matt -G wheel,input
$ passwd matt
$ env EDITOR=nano visudo # set wheel to allow sudo commands with pass
```
## A lot of packages to install
```
$ pacman -Sy htop pkgfile mlocate rsync tmux mosh usbutils wget git curl devtools mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon libva-mesa-driver lib32-libva-mesa-driver mesa-vdpau lib32-mesa-vdpau bash-completion fzf
$ pkgfile --update
```
## su matt
## Install paru
```
$ sudo pacman -S --needed git base-devel
$ git clone https://aur.archlinux.org/paru-git.git
$ cd paru-git
$ makepkg -si
$ sed -i 's/#Color/Color/' /etc/pacman.conf
$ sed -i 's/#IgnorePkg.*/IgnorePkg = linux-xanmod-anbox linux-xanmod-anbox-headers/' /etc/pacman.conf
$ cat << EOF >> /etc/paru.conf
CombinedUpgrade
BatchInstall
BottomUp
NoWarn = plymouth-theme-arch-elegant
EOF
```
## Audio
### ALSA
```
$ yay -Sy alsa-utils alsa-firmware sof-firmware alsa-ucm-conf
#unmute speakers
$ amixer sset Master unmute
```
### Pipewire
```
$ yay -Sy pipewire-audio pipewire-alsa pipewire-pulse
$ yay -R pulseaudio-alsa
$ sudo systemctl stop pulseaudio.service
$ systemctl --user enable --now pipewire-pulse.service
```
## Fingerprint Sensor Hack
```
$ yay -Sy python pam-fprint-grosshack
$ sudo systemctl enable --now fprintd
$ fprintd-enroll
```
### Use the reader
add this to the top of every file in /etc/pam.d/ that you want ie. polkit-1, sudo uwu
```
auth sufficient pam_fprintd_grosshack.so
auth sufficient pam_unix.so try_first_pass nullok
```
OR (for gtklock and check for sddm)
```
auth sufficient pam_fprintd.so
```
## Plymouth and Silent Boot
By following the wiki pages on [watchdogs](https://wiki.archlinux.org/title/Improving_performance#Watchdogs), [silent booting](https://wiki.archlinux.org/title/Silent_boot#top-page) and [Plymouth](https://wiki.archlinux.org/title/Plymouth), I edited my grub config and mkinitcpio, installed and setup Plymouth, to get a satisfying booting experience
```
$ yay -Sy plymouth-git
```
/etc/mkinitcpio.conf
```
$ sudo sed -i 's/MODULES=()/MODULES=(amdgpu)/' /etc/mkinitcpio.conf
$ sudo sed -i 's/#COMPRESSION="lz4"/COMPRESSION="lz4"/' /etc/mkinitcpio.conf
$ sudo sed -i 's/HOOKS=(.* /HOOKS=(base udev plymouth encrypt autodetect modconf kms keyboard keymap consolefont block filesystems fsck)/' /etc/mkinitcpio.conf
COMPRESSION="lz4"
```
/etc/default/grub
```
sudo sed -i 's/quiet loglevel 3/quiet splash loglevel=3 systemd.show_status=auto rd.udev.log_level=3 splash nowatchdog psi=1/' /etc/default/grub
```
Mute watchdog
```
$ echo blacklist sp5100_tco | sudo tee /etc/modprobe.d/disable-sp5100-watchdog.conf
```
Apply changes [Theme](https://github.com/murkl/plymouth-theme-arch-elegant)
```
$ git clone https://github.com/murkl/plymouth-theme-arch-elegant.git
$ cd plymouth-theme-arch-elegant/aur
$ makepkg -si
$ sudo plymouth-set-default-theme -R arch-elegant
$ sudo grub-mkconfig -o /boot/grub/grub.cfg
$ sudo sed -i 's/echo/#ech~o/g' /boot/grub/grub.cfg
```
## Here are some random changes and tweaks
### Firefox touchscreen [tweak](https://wiki.archlinux.org/title/Firefox/Tweaks#Enable_touchscreen_gestures)
```
$ echo MOZ_USE_XINPUT2 DEFAULT=1 | sudo tee -a /etc/security/pam_env.conf
```
then logout
### More Packages that are most likely needed
```
run toinstall.sh script
$ sudo reboot
```
### Flatpak
```
$ flatpak install com.github.iwalton3.jellyfin-media-player com.github.tchx84.Flatseal xournalpp stemlink
$ sudo flatpak override --filesystem=xdg-config/gtk-3.0
```
## Finally, install dotfiles
### get access to repo first
```
$ mkdir ~/git && cd ~/git
$ git clone git@git.nelim.org:matt1432/dotfiles.git
$ cd dotfiles
$ bash getenv.sh
$ sudo bash setup.sh
$ sudo bash fzf.sh /usr/share/fzf
```

View file

@ -1,2 +0,0 @@
use flake $FLAKE#node
npm ci

View file

@ -1,451 +0,0 @@
import eslint from '@eslint/js';
import jsdoc from 'eslint-plugin-jsdoc';
import stylistic from '@stylistic/eslint-plugin';
import tseslint from 'typescript-eslint';
export default tseslint.config({
files: ['**/*.js', '**/*.ts'],
ignores: ['node_modules/**', 'types/**'],
extends: [
eslint.configs.recommended,
jsdoc.configs['flat/recommended-typescript'],
stylistic.configs['recommended-flat'],
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
],
rules: {
// JSDoc settings
'jsdoc/tag-lines': ['warn', 'any', { startLines: 1 }],
'jsdoc/check-line-alignment': ['warn', 'always', {
tags: ['param', 'arg', 'argument', 'property', 'prop'],
}],
'jsdoc/no-types': 'off',
// Newer settings
'@typescript-eslint/no-extraneous-class': ['off'],
'@typescript-eslint/no-implied-eval': ['off'],
'class-methods-use-this': 'off',
'@stylistic/no-multiple-empty-lines': 'off',
// Pre-flat config
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'array-callback-return': [
'error',
{
allowImplicit: true,
checkForEach: true,
},
],
'no-constructor-return': [
'error',
],
'no-unreachable-loop': [
'error',
{
ignore: [
'ForInStatement',
'ForOfStatement',
],
},
],
'no-use-before-define': [
'error',
{
functions: false,
},
],
'block-scoped-var': [
'error',
],
'curly': [
'warn',
],
'default-case-last': [
'warn',
],
'default-param-last': [
'error',
],
'eqeqeq': [
'error',
'smart',
],
'func-names': [
'warn',
'never',
],
'func-style': [
'warn',
'expression',
],
'logical-assignment-operators': [
'warn',
'always',
],
'no-array-constructor': [
'error',
],
'no-empty-function': [
'warn',
],
'no-empty-static-block': [
'warn',
],
'no-extend-native': [
'error',
],
'no-extra-bind': [
'warn',
],
'no-implicit-coercion': [
'warn',
],
'no-iterator': [
'error',
],
'no-labels': [
'error',
],
'no-lone-blocks': [
'error',
],
'no-lonely-if': [
'error',
],
'no-loop-func': [
'error',
],
'no-magic-numbers': [
'error',
{
ignore: [
-1,
0.1,
0,
1,
2,
3,
4,
5,
10,
12,
33,
66,
100,
255,
360,
450,
500,
1000,
],
ignoreDefaultValues: true,
ignoreClassFieldInitialValues: true,
},
],
'no-multi-assign': [
'error',
],
'no-new-wrappers': [
'error',
],
'no-object-constructor': [
'error',
],
'no-proto': [
'error',
],
'no-return-assign': [
'error',
],
'no-sequences': [
'error',
],
'no-shadow': [
'error',
{
builtinGlobals: true,
allow: [
'Window',
],
},
],
'no-undef-init': [
'warn',
],
'no-undefined': [
'error',
],
'no-useless-constructor': [
'warn',
],
'no-useless-escape': [
'off',
],
'no-useless-return': [
'error',
],
'no-var': [
'error',
],
'no-void': [
'off',
],
'no-with': [
'error',
],
'object-shorthand': [
'error',
'always',
],
'one-var': [
'error',
'never',
],
'operator-assignment': [
'warn',
'always',
],
'prefer-arrow-callback': [
'error',
],
'prefer-const': [
'error',
],
'prefer-object-has-own': [
'error',
],
'prefer-regex-literals': [
'error',
],
'prefer-template': [
'warn',
],
'no-prototype-builtins': 'off',
'@typescript-eslint/no-var-requires': [
'off',
],
'@stylistic/array-bracket-newline': [
'warn',
'consistent',
],
'@stylistic/array-bracket-spacing': [
'warn',
'never',
],
'@stylistic/arrow-parens': [
'warn',
'always',
],
'@stylistic/brace-style': [
'warn',
'stroustrup',
],
'@stylistic/comma-dangle': [
'warn',
'always-multiline',
],
'@stylistic/comma-spacing': [
'warn',
{
before: false,
after: true,
},
],
'@stylistic/comma-style': [
'error',
'last',
],
'@stylistic/dot-location': [
'error',
'property',
],
'@stylistic/function-call-argument-newline': [
'warn',
'consistent',
],
'@stylistic/function-paren-newline': [
'warn',
'consistent',
],
'@stylistic/indent': [
'warn',
4,
{
SwitchCase: 1,
ignoreComments: true,
ignoredNodes: ['TemplateLiteral > *'],
},
],
'@stylistic/key-spacing': [
'warn',
{
beforeColon: false,
afterColon: true,
},
],
'@stylistic/keyword-spacing': [
'warn',
{
before: true,
},
],
'@stylistic/linebreak-style': [
'error',
'unix',
],
'@stylistic/lines-between-class-members': [
'warn',
'always',
{
exceptAfterSingleLine: true,
},
],
'@stylistic/max-len': [
'warn',
{
code: 105,
ignoreComments: true,
ignoreTrailingComments: true,
ignoreUrls: true,
},
],
'@stylistic/multiline-ternary': [
'warn',
'always-multiline',
],
'@stylistic/new-parens': [
'error',
],
'@stylistic/no-mixed-operators': [
'warn',
],
'@stylistic/no-mixed-spaces-and-tabs': [
'error',
],
'@stylistic/no-multi-spaces': [
'error',
],
'@stylistic/no-tabs': [
'error',
],
'@stylistic/no-trailing-spaces': [
'error',
],
'@stylistic/no-whitespace-before-property': [
'warn',
],
'@stylistic/nonblock-statement-body-position': [
'error',
'below',
],
'@stylistic/object-curly-newline': [
'warn',
{
consistent: true,
},
],
'@stylistic/object-curly-spacing': [
'warn',
'always',
],
'@stylistic/operator-linebreak': [
'warn',
'after',
],
'@stylistic/padded-blocks': [
'error',
'never',
],
'@stylistic/padding-line-between-statements': [
'warn',
{
blankLine: 'always',
prev: '*',
next: 'return',
},
{
blankLine: 'always',
prev: [
'const',
'let',
'var',
],
next: '*',
},
{
blankLine: 'any',
prev: [
'const',
'let',
'var',
],
next: [
'const',
'let',
'var',
],
},
{
blankLine: 'always',
prev: [
'case',
'default',
],
next: '*',
},
],
'@stylistic/quote-props': [
'error',
'consistent-as-needed',
],
'@stylistic/quotes': [
'error',
'single',
{
avoidEscape: true,
},
],
'@stylistic/semi': [
'error',
'always',
],
'@stylistic/semi-spacing': [
'warn',
],
'@stylistic/space-before-blocks': [
'warn',
],
'@stylistic/space-before-function-paren': [
'warn',
'never',
],
'@stylistic/space-infix-ops': [
'warn',
],
'@stylistic/spaced-comment': [
'warn',
'always',
],
'@stylistic/switch-colon-spacing': [
'warn',
],
'@stylistic/wrap-regex': [
'warn',
],
},
});

View file

@ -1,3 +0,0 @@
import eslintConf from './eslint.config';
export default eslintConf;

Binary file not shown.

View file

@ -1,16 +0,0 @@
{
"name": "eslint-conf",
"version": "0.0.0",
"type": "module",
"exports": "./index.ts",
"devDependencies": {
"@eslint/js": "9.17.0",
"@stylistic/eslint-plugin": "2.12.1",
"eslint": "9.17.0",
"eslint-plugin-jsdoc": "50.6.1",
"jiti": "2.4.2",
"pkg-types": "1.2.1",
"typescript": "5.7.2",
"typescript-eslint": "8.18.1"
}
}

View file

@ -1,25 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
// Env
"target": "ESNext",
"lib": [
"ESNext"
],
// Module
"module": "nodenext",
"moduleResolution": "bundler",
"baseUrl": ".",
// Emit
"noEmit": true,
"newLine": "LF",
// Interop
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
// Type Checking
"strict": true,
"noImplicitAny": false
}
}

View file

@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

View file

@ -1,20 +0,0 @@
{
inputs,
pkgs,
...
}: let
inherit (pkgs.lib) getExe listToAttrs nameValuePair;
buildApp = attrs: (pkgs.callPackage ./nix/buildApp.nix ({} // inputs // attrs));
mkApp = file: {
program = getExe (pkgs.callPackage file ({inherit buildApp;} // inputs));
type = "app";
};
mkApps = apps: listToAttrs (map (x: nameValuePair x (mkApp ./${x})) apps);
in
mkApps [
"extract-subs"
"update-sources"
]

View file

@ -1,2 +0,0 @@
use flake $FLAKE#subtitles-dev
npm ci

View file

@ -1,13 +0,0 @@
{
buildApp,
ffmpeg-full,
...
}:
buildApp {
src = ./.;
npmDepsHash = "sha256-ytaO1mqoORNB5rP/8X/WpIoHkxmi1+Pc6Nep02+fTZ8=";
runtimeInputs = [
ffmpeg-full
];
}

View file

@ -1,3 +0,0 @@
import eslintConf from 'eslint-conf';
export default eslintConf;

Binary file not shown.

View file

@ -1,21 +0,0 @@
{
"name": "extract-subs",
"version": "0.0.0",
"bin": "out/bin/app.cjs",
"type": "module",
"scripts": {
"build": "node_ver=$(node -v); esbuild src/app.ts --bundle --platform=node --target=\"node${node_ver:1:2}\" --outfile=out/bin/app.cjs"
},
"dependencies": {
"@types/fluent-ffmpeg": "2.1.27",
"fluent-ffmpeg": "2.1.3"
},
"devDependencies": {
"eslint-conf": "file:../config",
"@types/node": "22.10.2",
"esbuild": "0.24.2",
"eslint": "9.17.0",
"jiti": "2.4.2",
"typescript": "5.7.2"
}
}

View file

@ -1,157 +0,0 @@
import { spawnSync as spawn } from 'child_process';
import ffprobe from './ffprobe';
import { ISO6393To1 } from './lang-codes';
/* Types */
import { FfprobeStream } from 'fluent-ffmpeg';
const SPAWN_OPTS = {
stdio: [process.stdin, process.stdout, process.stderr],
};
/**
* These are the cli arguments
*
* @param videoPath the directory in which we want to sync the subtitles
* @param languages a comma-separated list of languages (3 letters) to sync the subtitles
*/
const video = process.argv[2];
const languages = process.argv[3]?.split(',');
// Global Vars
const subIndexes: number[] = [];
let videoPath: string;
let baseName: string;
/**
* Gets the relative path to the subtitle file of a ffmpeg stream.
*
* @param sub the stream of the subtitles to extract
* @returns the path of the subtitle file
*/
const getSubPath = (sub: FfprobeStream): string => {
const language = ISO6393To1.get(sub.tags.language);
const forced = sub.disposition?.forced === 0 ?
'' :
'.forced';
const hearingImpaired = sub.disposition?.hearing_impaired === 0 ?
'' :
'.sdh';
return `${baseName}${forced}.${language}${hearingImpaired}.srt`;
};
/**
* Removes all subtitles streams from the video file.
*/
const removeContainerSubs = (): void => {
spawn('mv', [
videoPath,
`${videoPath}.bak`,
], SPAWN_OPTS);
spawn('ffmpeg', [
'-i', `${videoPath}.bak`,
'-map', '0',
...subIndexes.map((i) => ['-map', `-0:${i}`]).flat(),
'-c', 'copy', videoPath,
], SPAWN_OPTS);
spawn('rm', [
`${videoPath}.bak`,
], SPAWN_OPTS);
};
/**
* Extracts a sub of a video file to a subtitle file.
*
* @param sub the stream of the subtitles to extract
*/
const extractSub = (sub: FfprobeStream): void => {
const subFile = getSubPath(sub);
spawn('ffmpeg', [
'-i', videoPath,
'-map', `0:${sub.index}`, subFile,
], SPAWN_OPTS);
subIndexes.push(sub.index);
};
/**
* Sorts the list of streams to only keep subtitles
* that can be extracted.
*
* @param lang the language of the subtitles
* @param streams the streams
* @returns the streams that represent subtitles
*/
const findSubs = (
lang: string,
streams: FfprobeStream[],
): FfprobeStream[] => {
const subs = streams.filter((s) => s.tags?.language &&
s.tags.language === lang &&
s.codec_type === 'subtitle');
const pgs = subs.filter((s) => s.codec_name === 'hdmv_pgs_subtitle');
// If we only have PGS subs, warn user
if (pgs.length === subs.length) {
console.warn(`No SRT subtitle tracks were found for ${lang}`);
}
// Remove PGS streams from subs
return subs.filter((s) => s.codec_name !== 'hdmv_pgs_subtitle');
};
/**
* Where the magic happens.
*/
const main = async(): Promise<void> => {
// Get rid of video extension
baseName = videoPath.split('/').at(-1)!.replace(/\.[^.]*$/, '');
// ffprobe the video file to see available sub tracks
const data = await ffprobe(videoPath);
if (!data?.streams) {
console.error('Couldn\'t find streams in video file');
return;
}
// Check for languages wanted
languages.forEach((lang) => {
const subs = findSubs(lang, data.streams);
if (subs.length === 0) {
console.warn(`No subtitle tracks were found for ${lang}`);
return;
}
// Extract all subs
subs.forEach((sub) => { extractSub(sub); });
});
removeContainerSubs();
};
// Check if there are 2 params
if (video && languages) {
videoPath = video;
main();
}
else {
console.error('Error: no argument passed');
process.exit(1);
}

View file

@ -1,8 +0,0 @@
import Ffmpeg from 'fluent-ffmpeg';
export default (videoPath: string) => new Promise<Ffmpeg.FfprobeData>((resolve) => {
Ffmpeg.ffprobe(videoPath, (_e, data) => {
resolve(data);
});
});

View file

@ -1,373 +0,0 @@
export const ISO6391To3 = new Map([
['aa', 'aar'],
['ab', 'abk'],
['af', 'afr'],
['ak', 'aka'],
['am', 'amh'],
['ar', 'ara'],
['an', 'arg'],
['as', 'asm'],
['av', 'ava'],
['ae', 'ave'],
['ay', 'aym'],
['az', 'aze'],
['ba', 'bak'],
['bm', 'bam'],
['be', 'bel'],
['bn', 'ben'],
['bi', 'bis'],
['bo', 'bod'],
['bs', 'bos'],
['br', 'bre'],
['bg', 'bul'],
['ca', 'cat'],
['cs', 'ces'],
['ch', 'cha'],
['ce', 'che'],
['cu', 'chu'],
['cv', 'chv'],
['kw', 'cor'],
['co', 'cos'],
['cr', 'cre'],
['cy', 'cym'],
['da', 'dan'],
['de', 'deu'],
['dv', 'div'],
['dz', 'dzo'],
['el', 'ell'],
['en', 'eng'],
['eo', 'epo'],
['et', 'est'],
['eu', 'eus'],
['ee', 'ewe'],
['fo', 'fao'],
['fa', 'fas'],
['fj', 'fij'],
['fi', 'fin'],
['fr', 'fre'],
['fy', 'fry'],
['ff', 'ful'],
['gd', 'gla'],
['ga', 'gle'],
['gl', 'glg'],
['gv', 'glv'],
['gn', 'grn'],
['gu', 'guj'],
['ht', 'hat'],
['ha', 'hau'],
['sh', 'hbs'],
['he', 'heb'],
['hz', 'her'],
['hi', 'hin'],
['ho', 'hmo'],
['hr', 'hrv'],
['hu', 'hun'],
['hy', 'hye'],
['ig', 'ibo'],
['io', 'ido'],
['ii', 'iii'],
['iu', 'iku'],
['ie', 'ile'],
['ia', 'ina'],
['id', 'ind'],
['ik', 'ipk'],
['is', 'isl'],
['it', 'ita'],
['jv', 'jav'],
['ja', 'jpn'],
['kl', 'kal'],
['kn', 'kan'],
['ks', 'kas'],
['ka', 'kat'],
['kr', 'kau'],
['kk', 'kaz'],
['km', 'khm'],
['ki', 'kik'],
['rw', 'kin'],
['ky', 'kir'],
['kv', 'kom'],
['kg', 'kon'],
['ko', 'kor'],
['kj', 'kua'],
['ku', 'kur'],
['lo', 'lao'],
['la', 'lat'],
['lv', 'lav'],
['li', 'lim'],
['ln', 'lin'],
['lt', 'lit'],
['lb', 'ltz'],
['lu', 'lub'],
['lg', 'lug'],
['mh', 'mah'],
['ml', 'mal'],
['mr', 'mar'],
['mk', 'mkd'],
['mg', 'mlg'],
['mt', 'mlt'],
['mn', 'mon'],
['mi', 'mri'],
['ms', 'msa'],
['my', 'mya'],
['na', 'nau'],
['nv', 'nav'],
['nr', 'nbl'],
['nd', 'nde'],
['ng', 'ndo'],
['ne', 'nep'],
['nl', 'nld'],
['nn', 'nno'],
['nb', 'nob'],
['no', 'nor'],
['ny', 'nya'],
['oc', 'oci'],
['oj', 'oji'],
['or', 'ori'],
['om', 'orm'],
['os', 'oss'],
['pa', 'pan'],
['pi', 'pli'],
['pl', 'pol'],
['pt', 'por'],
['ps', 'pus'],
['qu', 'que'],
['rm', 'roh'],
['ro', 'ron'],
['rn', 'run'],
['ru', 'rus'],
['sg', 'sag'],
['sa', 'san'],
['si', 'sin'],
['sk', 'slk'],
['sl', 'slv'],
['se', 'sme'],
['sm', 'smo'],
['sn', 'sna'],
['sd', 'snd'],
['so', 'som'],
['st', 'sot'],
['es', 'spa'],
['sq', 'sqi'],
['sc', 'srd'],
['sr', 'srp'],
['ss', 'ssw'],
['su', 'sun'],
['sw', 'swa'],
['sv', 'swe'],
['ty', 'tah'],
['ta', 'tam'],
['tt', 'tat'],
['te', 'tel'],
['tg', 'tgk'],
['tl', 'tgl'],
['th', 'tha'],
['ti', 'tir'],
['to', 'ton'],
['tn', 'tsn'],
['ts', 'tso'],
['tk', 'tuk'],
['tr', 'tur'],
['tw', 'twi'],
['ug', 'uig'],
['uk', 'ukr'],
['ur', 'urd'],
['uz', 'uzb'],
['ve', 'ven'],
['vi', 'vie'],
['vo', 'vol'],
['wa', 'wln'],
['wo', 'wol'],
['xh', 'xho'],
['yi', 'yid'],
['yo', 'yor'],
['za', 'zha'],
['zh', 'zho'],
['zu', 'zul'],
]);
export const ISO6393To1 = new Map([
['aar', 'aa'],
['abk', 'ab'],
['afr', 'af'],
['aka', 'ak'],
['amh', 'am'],
['ara', 'ar'],
['arg', 'an'],
['asm', 'as'],
['ava', 'av'],
['ave', 'ae'],
['aym', 'ay'],
['aze', 'az'],
['bak', 'ba'],
['bam', 'bm'],
['bel', 'be'],
['ben', 'bn'],
['bis', 'bi'],
['bod', 'bo'],
['bos', 'bs'],
['bre', 'br'],
['bul', 'bg'],
['cat', 'ca'],
['ces', 'cs'],
['cha', 'ch'],
['che', 'ce'],
['chu', 'cu'],
['chv', 'cv'],
['cor', 'kw'],
['cos', 'co'],
['cre', 'cr'],
['cym', 'cy'],
['dan', 'da'],
['deu', 'de'],
['div', 'dv'],
['dzo', 'dz'],
['ell', 'el'],
['eng', 'en'],
['epo', 'eo'],
['est', 'et'],
['eus', 'eu'],
['ewe', 'ee'],
['fao', 'fo'],
['fas', 'fa'],
['fij', 'fj'],
['fin', 'fi'],
['fre', 'fr'],
['fry', 'fy'],
['ful', 'ff'],
['gla', 'gd'],
['gle', 'ga'],
['glg', 'gl'],
['glv', 'gv'],
['grn', 'gn'],
['guj', 'gu'],
['hat', 'ht'],
['hau', 'ha'],
['hbs', 'sh'],
['heb', 'he'],
['her', 'hz'],
['hin', 'hi'],
['hmo', 'ho'],
['hrv', 'hr'],
['hun', 'hu'],
['hye', 'hy'],
['ibo', 'ig'],
['ido', 'io'],
['iii', 'ii'],
['iku', 'iu'],
['ile', 'ie'],
['ina', 'ia'],
['ind', 'id'],
['ipk', 'ik'],
['isl', 'is'],
['ita', 'it'],
['jav', 'jv'],
['jpn', 'ja'],
['kal', 'kl'],
['kan', 'kn'],
['kas', 'ks'],
['kat', 'ka'],
['kau', 'kr'],
['kaz', 'kk'],
['khm', 'km'],
['kik', 'ki'],
['kin', 'rw'],
['kir', 'ky'],
['kom', 'kv'],
['kon', 'kg'],
['kor', 'ko'],
['kua', 'kj'],
['kur', 'ku'],
['lao', 'lo'],
['lat', 'la'],
['lav', 'lv'],
['lim', 'li'],
['lin', 'ln'],
['lit', 'lt'],
['ltz', 'lb'],
['lub', 'lu'],
['lug', 'lg'],
['mah', 'mh'],
['mal', 'ml'],
['mar', 'mr'],
['mkd', 'mk'],
['mlg', 'mg'],
['mlt', 'mt'],
['mon', 'mn'],
['mri', 'mi'],
['msa', 'ms'],
['mya', 'my'],
['nau', 'na'],
['nav', 'nv'],
['nbl', 'nr'],
['nde', 'nd'],
['ndo', 'ng'],
['nep', 'ne'],
['nld', 'nl'],
['nno', 'nn'],
['nob', 'nb'],
['nor', 'no'],
['nya', 'ny'],
['oci', 'oc'],
['oji', 'oj'],
['ori', 'or'],
['orm', 'om'],
['oss', 'os'],
['pan', 'pa'],
['pli', 'pi'],
['pol', 'pl'],
['por', 'pt'],
['pus', 'ps'],
['que', 'qu'],
['roh', 'rm'],
['ron', 'ro'],
['run', 'rn'],
['rus', 'ru'],
['sag', 'sg'],
['san', 'sa'],
['sin', 'si'],
['slk', 'sk'],
['slv', 'sl'],
['sme', 'se'],
['smo', 'sm'],
['sna', 'sn'],
['snd', 'sd'],
['som', 'so'],
['sot', 'st'],
['spa', 'es'],
['sqi', 'sq'],
['srd', 'sc'],
['srp', 'sr'],
['ssw', 'ss'],
['sun', 'su'],
['swa', 'sw'],
['swe', 'sv'],
['tah', 'ty'],
['tam', 'ta'],
['tat', 'tt'],
['tel', 'te'],
['tgk', 'tg'],
['tgl', 'tl'],
['tha', 'th'],
['tir', 'ti'],
['ton', 'to'],
['tsn', 'tn'],
['tso', 'ts'],
['tuk', 'tk'],
['tur', 'tr'],
['twi', 'tw'],
['uig', 'ug'],
['ukr', 'uk'],
['urd', 'ur'],
['uzb', 'uz'],
['ven', 've'],
['vie', 'vi'],
['vol', 'vo'],
['wln', 'wa'],
['wol', 'wo'],
['xho', 'xh'],
['yid', 'yi'],
['yor', 'yo'],
['zha', 'za'],
['zho', 'zh'],
['zul', 'zu'],
]);

View file

@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../config/tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

View file

@ -1,39 +0,0 @@
{
runtimeInputs,
npmDepsHash,
src,
lib,
buildNpmPackage,
makeWrapper,
nodejs_latest,
jq,
...
}: let
inherit (lib) concatMapStringsSep;
inherit (builtins) fromJSON readFile;
packageJSON = fromJSON (readFile "${src}/package.json");
in
buildNpmPackage rec {
pname = packageJSON.name;
inherit (packageJSON) version;
inherit src runtimeInputs npmDepsHash;
prePatch = ''
mv ./tsconfig.json ./project.json
sed 's/^ *\/\/.*//' ${../config/tsconfig.json} > ./base.json
${jq}/bin/jq -sr '.[0] * .[1] | del(.extends)' ./project.json ./base.json > ./tsconfig.json
rm base.json project.json
'';
nativeBuildInputs = [makeWrapper];
postInstall = ''
wrapProgram $out/bin/${pname} \
--prefix PATH : ${concatMapStringsSep ":" (p: p + "/bin") runtimeInputs}
'';
nodejs = nodejs_latest;
meta.mainProgram = pname;
}

View file

@ -1,14 +0,0 @@
{
pkgs,
self,
...
}: let
inherit (pkgs.lib) mapAttrs removeSuffix;
in
mapAttrs (
name: app: (pkgs.symlinkJoin {
name = "app-${name}";
paths = [(removeSuffix "/bin/${name}" (toString app.program))];
})
)
(removeAttrs self.apps.${pkgs.system} ["genflake"])

View file

@ -1,2 +0,0 @@
use flake $FLAKE#node
npm ci

View file

@ -1,19 +0,0 @@
{
buildApp,
callPackage,
nix-update,
nodejs_latest,
prefetch-npm-deps,
...
}:
buildApp {
src = ./.;
npmDepsHash = "sha256-vIhR/+Pgj35sv/ZX1iL+xWheu6w7vQeJy4OEfa8tXFw=";
runtimeInputs = [
nix-update
nodejs_latest
prefetch-npm-deps
(callPackage ../../modules/docker/updateImage.nix {})
];
}

View file

@ -1,3 +0,0 @@
import eslintConf from 'eslint-conf';
export default eslintConf;

Binary file not shown.

View file

@ -1,18 +0,0 @@
{
"name": "update-sources",
"version": "0.0.0",
"bin": "out/bin/app.cjs",
"type": "module",
"scripts": {
"build": "node_ver=$(node -v); esbuild src/app.ts --bundle --platform=node --target=\"node${node_ver:1:2}\" --outfile=out/bin/app.cjs"
},
"devDependencies": {
"eslint-conf": "file:../config",
"@types/node": "22.10.2",
"esbuild": "0.24.2",
"eslint": "9.17.0",
"jiti": "2.4.2",
"pkg-types": "1.2.1",
"typescript": "5.7.2"
}
}

View file

@ -1,153 +0,0 @@
import { spawnSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { parseArgs } from './lib';
import { updateDocker } from './docker';
import { updateFirefoxAddons } from '././firefox';
import { updateFlakeInputs } from './flake';
import updateNodeModules from './node-modules';
import {
runNixUpdate,
updateCustomPackage,
updateVuetorrent,
} from './misc';
/* Constants */
const FLAKE = process.env.FLAKE;
if (!FLAKE) {
console.error('Env var FLAKE not found');
process.exit(1);
}
const args = parseArgs();
const main = async() => {
if (args['d'] || args['docker']) {
console.log(updateDocker());
}
if (args['i'] || args['inputs']) {
console.log(updateFlakeInputs());
}
if (args['f'] || args['firefox']) {
console.log(updateFirefoxAddons());
}
if (args['v'] || args['vuetorrent']) {
console.log(updateVuetorrent());
}
if (args['c'] || args['custom-sidebar']) {
console.log(updateCustomPackage(
'scopedPackages.x86_64-linux.lovelace-components.custom-sidebar',
));
}
if (args['s'] || args['some-sass-language-server']) {
console.log(updateCustomPackage('some-sass-language-server'));
}
if (args['n'] || args['node_modules']) {
console.log(await updateNodeModules());
}
if (args['h'] || args['homepage']) {
console.log(runNixUpdate('homepage'));
}
if (args['a'] || args['all']) {
// Update this first because of nix run cmd
const firefoxOutput = updateFirefoxAddons();
console.log(firefoxOutput);
const flakeOutput = updateFlakeInputs();
console.log(flakeOutput);
const dockerOutput = updateDocker();
console.log(dockerOutput);
const nodeModulesOutput = await updateNodeModules();
console.log(nodeModulesOutput);
const vuetorrentOutput = updateVuetorrent();
console.log(vuetorrentOutput);
// This doesn't need to be added to commit msgs
console.log(updateCustomPackage(
'scopedPackages.x86_64-linux.lovelace-components.custom-sidebar',
));
console.log(updateCustomPackage('some-sass-language-server'));
// nix-update executions
let nixUpdateOutputs = '';
const updatePackage = (pkg: string): void => {
const execution = runNixUpdate(pkg);
nixUpdateOutputs += execution.stdout;
console.log(execution.stderr);
console.log(execution.stdout);
};
updatePackage('homepage');
spawnSync('nixFastBuild', [], {
shell: true,
stdio: [process.stdin, process.stdout, process.stderr],
});
const indentOutput = (output: string): string => {
return ` ${output.split('\n').join('\n ')}`;
};
const output = [
'chore: update sources\n\n',
];
if (flakeOutput.length > 5) {
output.push(`Flake Inputs:\n${indentOutput(flakeOutput)}\n\n`);
}
if (dockerOutput.length > 5) {
output.push(`Docker Images:\n${indentOutput(dockerOutput)}\n`);
}
if (firefoxOutput.length > 5) {
output.push(`Firefox Addons:\n${indentOutput(firefoxOutput)}\n\n`);
}
if (nodeModulesOutput.length > 5) {
output.push(`Node modules:\n${indentOutput(nodeModulesOutput)}\n`);
}
if (vuetorrentOutput.length > 5) {
output.push(`Misc Sources:\n${indentOutput(vuetorrentOutput)}\n\n`);
}
if (nixUpdateOutputs.length > 5) {
output.push(`nix-update executions:\n${indentOutput(nixUpdateOutputs)}\n`);
}
if (args['f']) {
writeFileSync(args['f'] as string, output.join(''));
}
else {
console.log(output.join(''));
}
}
spawnSync('alejandra', ['-q', FLAKE], { shell: true });
};
main();

View file

@ -1,33 +0,0 @@
import { readdirSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
/* Constants */
const FLAKE = process.env.FLAKE;
const updateImages = (imagePath: string): string | undefined => {
console.log(`Updating ${imagePath.split('/').at(-1)} images`);
const out = spawnSync('updateImages', [imagePath], { shell: true }).stdout.toString();
if (!out.startsWith('# Locked')) {
return out;
}
};
export const updateDocker = () => {
let updates = '';
updates += updateImages(`${FLAKE}/configurations/nos/modules/jellyfin`) ?? '';
updates += updateImages(`${FLAKE}/configurations/homie/modules/home-assistant/netdaemon`) ?? '';
const DIR = `${FLAKE}/configurations/nos/modules/docker`;
readdirSync(DIR, { withFileTypes: true, recursive: true }).forEach((path) => {
if (path.name === 'compose.nix') {
updates += updateImages(path.parentPath) ?? '';
}
});
return updates;
};

View file

@ -1,61 +0,0 @@
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
/* Constants */
const FLAKE = process.env.FLAKE;
export const updateFirefoxAddons = () => {
console.log('Updating firefox addons using mozilla-addons-to-nix');
const DIR = `${FLAKE}/scopedPackages/firefox-addons`;
const GENERATED_FILE = `${DIR}/generated-firefox-addons.nix`;
const SLUGS = `${DIR}/addons.json`;
const nameMap = Object.fromEntries([...JSON.parse(readFileSync(SLUGS, 'utf-8'))]
.map((addon) => [addon.slug, addon.pname || addon.slug]));
const nixExpr = `'
x: let
inherit (builtins) attrValues filter hasAttr isAttrs map;
in
map (d: d.name) (filter (y:
isAttrs y &&
hasAttr "type" y &&
y.type == "derivation") (attrValues x))
'`;
const OLD_VERS = Object.fromEntries([...JSON.parse(spawnSync('nix', [
'eval',
'.#scopedPackages.x86_64-linux.firefoxAddons',
'--apply',
nixExpr,
'--json',
], { shell: true }).stdout.toString())]
.map((p) => {
const pname = p.replace(/-[0-9].*$/, '');
return [pname, p.replace(`${pname}-`, '')];
})
.filter((pinfo) => pinfo[0] !== 'frankerfacez'));
const NEW_VERS = Object.fromEntries(spawnSync(
'nix',
['run', 'sourcehut:~rycee/mozilla-addons-to-nix',
SLUGS, GENERATED_FILE],
{ shell: true },
).stdout
.toString()
.split('\n')
.map((p) => {
const pinfo = p.replace('Fetched ', '').split(' ');
return [nameMap[pinfo[0]], pinfo[2]];
}));
return Object.keys(OLD_VERS)
.sort()
.filter((pname) => OLD_VERS[pname] !== NEW_VERS[pname])
.map((pname) => `${pname}: ${OLD_VERS[pname]} -> ${NEW_VERS[pname]}`)
.join('\n');
};

View file

@ -1,44 +0,0 @@
import { spawnSync } from 'node:child_process';
/* Constants */
const FLAKE = process.env.FLAKE;
export const updateFlakeInputs = () => {
const output = spawnSync(
`git restore flake.lock &> /dev/null; nix flake update --flake ${FLAKE}` +
' |& grep -v "warning: updating lock file" |& grep -v "unpacking"',
[],
{ shell: true },
).stdout
.toString()
// Add an extra blank line between inputs
.split('\n•')
// Filter out some inputs
.filter((input) => ![
'systems',
'flake-utils',
'flake-parts',
'treefmt-nix',
'lib-aggregate',
'lib-aggregate/nixpkgs-lib',
'sops-nix/nixpkgs-stable',
'discord-overlay/Vencord-src',
'nix-gaming/umu',
].some((inputName) => input.startsWith(` Updated input '${inputName}'`)))
.join('\n\n•')
// help readability of git revs
.split('\n')
.map((l) => l
.replace(
/\/(.{40})\?narHash=sha256[^']*(.*)/,
(_, backref1, backref2) => `${backref2} rev: ${backref1}`,
)
.replace(
/\?ref.*&rev=(.{40})[^'&]*(.*)/,
(_, backref1, backref2) => `${backref2} rev: ${backref1}`,
))
.join('\n');
return output;
};

View file

@ -1,43 +0,0 @@
import { spawnSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
export const parseArgs = () => {
const args = {} as Record<string, unknown>;
let lastFlag: string | null = null;
for (let i = 2; i < process.argv.length; ++i) {
const arg = process.argv[i];
if (arg.toString().startsWith('-')) {
lastFlag = arg.toString().replace(/^-{1,2}/, '');
args[lastFlag] = true;
}
else if (lastFlag) {
args[lastFlag] = arg;
lastFlag = null;
}
else {
console.error(`Could not parse args: ${arg.toString()}`);
}
}
return args;
};
export const parseFetchurl = (url: string) => JSON.parse(spawnSync(
'nix', ['store', 'prefetch-file', '--refresh', '--json',
'--hash-type', 'sha256', url, '--name', '"escaped"'], { shell: true },
).stdout.toString()).hash;
export const replaceInFile = (replace: RegExp, replacement: string, file: string) => {
const fileContents = readFileSync(file);
const replaced = fileContents.toString().replace(replace, replacement);
writeFileSync(file, replaced);
};
export const npmRun = (args: string[], workspaceDir: string) => spawnSync(
'npm', args, { cwd: workspaceDir },
).stdout.toString();

View file

@ -1,73 +0,0 @@
import { writeFileSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { parseFetchurl } from './lib';
/* Constants */
const FLAKE = process.env.FLAKE;
const genVueText = (
version: string,
hash: string,
url: string,
) => `# This file was autogenerated. DO NOT EDIT!
{
version = "${version}";
url = "${url}";
hash = "${hash}";
}
`;
export const updateVuetorrent = () => {
const FILE = `${FLAKE}/configurations/nos/modules/qbittorrent/vuetorrent.nix`;
const OLD_VERSION = JSON.parse(spawnSync('nix',
['eval', '-f', FILE, '--json'],
{ shell: true }).stdout.toString()).version;
const VERSION = JSON.parse(spawnSync('curl',
['-s', 'https://api.github.com/repos/VueTorrent/VueTorrent/releases/latest'],
{ shell: true }).stdout.toString()).tag_name.replace('v', '');
const URL = `https://github.com/VueTorrent/VueTorrent/releases/download/v${VERSION}/vuetorrent.zip`;
const HASH = parseFetchurl(URL);
const fileText = genVueText(VERSION, HASH, URL);
writeFileSync(FILE, fileText);
return OLD_VERSION !== VERSION ? `Vuetorrent: ${OLD_VERSION} -> ${VERSION}` : '';
};
export const updateCustomPackage = (pkg: string) => spawnSync(
`nix run ${FLAKE}#${pkg}.update`,
[],
{ shell: true },
).stderr.toString();
const getAttrVersion = (attr: string): string => spawnSync('nix',
['eval', '--raw', `${FLAKE}#${attr}.version`],
{ shell: true }).stdout.toString();
export const runNixUpdate = (
attr: string,
options: string[] = [],
): { stdout: string, stderr: string } => {
const OLD_VERSION = getAttrVersion(attr);
const execution = spawnSync(
`nix-update --flake ${attr} --write-commit-message >(head -n 1 -) > /dev/null`,
options,
{ shell: true, cwd: FLAKE },
);
const NEW_VERSION = getAttrVersion(attr);
return {
stdout: OLD_VERSION !== NEW_VERSION ? execution.stdout.toString() : '',
stderr: execution.stderr.toString(),
};
};

View file

@ -1,82 +0,0 @@
import { readPackageJSON, writePackageJSON } from 'pkg-types';
import { readdirSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { replaceInFile, npmRun } from './lib';
/* Constants */
const FLAKE = process.env.FLAKE as string;
const updatePackageJson = async(workspaceDir: string, updates: object) => {
const currentPackageJson = await readPackageJSON(`${workspaceDir}/package.json`);
const outdated = JSON.parse(npmRun(['outdated', '--json'], workspaceDir));
const updateDeps = (deps: string) => {
Object.keys(currentPackageJson[deps]).forEach((dep) => {
const versions = outdated[dep];
const current = versions?.wanted || versions?.current;
if (!current) {
return;
}
if (current !== versions.latest) {
updates[dep] = `${current} -> ${versions.latest}`;
}
currentPackageJson[deps][dep] = versions.latest;
});
};
if (currentPackageJson.dependencies) {
updateDeps('dependencies');
}
if (currentPackageJson.devDependencies) {
updateDeps('devDependencies');
}
await writePackageJSON(`${workspaceDir}/package.json`, currentPackageJson);
};
const prefetchNpmDeps = (workspaceDir: string): string => {
npmRun(['install', '--package-lock-only'], workspaceDir);
return spawnSync(
'prefetch-npm-deps',
[`${workspaceDir}/package-lock.json`],
).stdout.toString().replace('\n', '');
};
export default async() => {
const updates = {};
const packages = readdirSync(FLAKE, { withFileTypes: true, recursive: true });
for (const path of packages) {
if (
path.name === 'package.json' &&
!path.parentPath.includes('node_modules') &&
!path.parentPath.includes('apps/config')
) {
await updatePackageJson(path.parentPath, updates);
const hash = prefetchNpmDeps(path.parentPath);
replaceInFile(
/npmDepsHash = ".*";/,
`npmDepsHash = "${hash}";`,
`${path.parentPath}/default.nix`,
);
}
}
return Object.entries(updates)
.map(([key, dep]) => `${key}: ${dep}`)
.join('\n');
};

View file

@ -1,10 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../config/tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

129
conf/pkgs.log Normal file
View file

@ -0,0 +1,129 @@
acpi
alacritty
asp
bat
blueman
bluez
bluez-plugins
bluez-tools
bluez-utils
brightnessctl
cliphist
cpio
cups
discord
doctest
dolphin
dosfstools
exfatprogs
f2fs-tools
ffmpeg-compat-57
ffmpegthumbs
firefox
flatery-icon-theme-git
fwupd
galaxybudsclient-bin
gimp
git-lfs
gnome-bluetooth-3.0
gnome-calculator
gnome-keyring
gnome-themes-extra
gparted
grim
grub-customizer
gtklock
gtklock-playerctl-module
gtklock-powerbar-module
gtklock-userinfo-module
hplip
hyprpaper-git
iio-sensor-proxy-git
jdk-temurin
kde-cli-tools
kdegraphics-thumbnailers
kimageformats
kio-admin
kvantum
libinput-gestures-git
libreoffice-fresh
light
lostfiles
nano
neofetch
neovim-git
network-manager-applet
nextcloud-client
nilfs-utils
nm-connection-editor
nwg-look-bin
otf-font-awesome
pavucontrol
photoqt
pkgfile
pokemon-colorscripts-git
polkit-kde-agent
progress
python-evdev
python-libevdev
python-pyclip
python-pyqt5
python-pyqt5-3d
python-pyqt5-chart
python-pyqt5-datavisualization
python-pyqt5-networkauth
python-pyqt5-purchasing
python-pyqt5-webengine
python-pyqt6
python-pyqt6-3d
python-pyqt6-charts
python-pyqt6-datavisualization
python-pyqt6-networkauth
python-pyqt6-webengine
python-uinput
qt5-charts
qt5-imageformats
qt5ct
reiserfsprogs
ripgrep
rofi-lbonn-wayland-only-git
rustup
sddm-git
seahorse
slurp
spicetify-cli-git
spicetify-themes-git
spotify-snapstore
spotifywm-git
squeekboard-git
swappy-git
swayidle
swaync-git
swayosd-git
tablet-mode
tlp
ttf-font-awesome
ttf-go-nerd
ttf-jetbrains-mono
ttf-jetbrains-mono-nerd
ttf-meslo-nerd-font-powerlevel10k
ttf-ms-win11-auto
ttf-nerd-fonts-symbols
ttf-nerd-fonts-symbols-common
ttf-space-mono-nerd
ttf-ubuntu-font-family
ttf-ubuntu-nerd
tutanota-desktop-bin
waybar-hyprland-git
waydroid-git
wev
wine-staging
winetricks
wl-clip-persist-git
wl-color-picker
xclip
xdg-desktop-portal-hyprland-git
xdg-utils
xorg-xwayland
zathura-pdf-mupdf
zenity

1
conf/toinstall.sh Executable file
View file

@ -0,0 +1 @@
yay -Sy acpi alacritty asp bat blueman bluez bluez-plugins bluez-tools bluez-utils brightnessctl cliphist cpio cups discord doctest dolphin dosfstools exfatprogs f2fs-tools ffmpeg-compat-57 ffmpegthumbs firefox flatery-icon-theme-git fwupd galaxybudsclient-bin gimp git-lfs gnome-bluetooth-3.0 gnome-calculator gnome-keyring gnome-themes-extra gparted grim grub-customizer gtklock gtklock-playerctl-module gtklock-powerbar-module gtklock-userinfo-module hplip hyprpaper-git iio-sensor-proxy-git jdk-temurin kde-cli-tools kdegraphics-thumbnailers kimageformats kio-admin kvantum libinput-gestures-git libreoffice-fresh light lostfiles nano neofetch neovim-git network-manager-applet nextcloud-client nilfs-utils nm-connection-editor nwg-look-bin otf-font-awesome pavucontrol photoqt pkgfile pokemon-colorscripts-git polkit-kde-agent progress python-evdev python-libevdev python-pyclip python-pyqt5 python-pyqt5-3d python-pyqt5-chart python-pyqt5-datavisualization python-pyqt5-networkauth python-pyqt5-purchasing python-pyqt5-webengine python-pyqt6 python-pyqt6-3d python-pyqt6-charts python-pyqt6-datavisualization python-pyqt6-networkauth python-pyqt6-webengine python-uinput qt5-charts qt5-imageformats qt5ct reiserfsprogs ripgrep rofi-lbonn-wayland-only-git rustup sddm-git seahorse slurp spicetify-cli-git spicetify-themes-git spotify-snapstore spotifywm-git squeekboard-git swappy-git swayidle swaync-git swayosd-git tablet-mode tlp ttf-font-awesome ttf-go-nerd ttf-jetbrains-mono ttf-jetbrains-mono-nerd ttf-meslo-nerd-font-powerlevel10k ttf-ms-win11-auto ttf-nerd-fonts-symbols ttf-nerd-fonts-symbols-common ttf-space-mono-nerd ttf-ubuntu-font-family ttf-ubuntu-nerd tutanota-desktop-bin waybar-hyprland-git waydroid-git wev wine-staging winetricks wl-clip-persist-git wl-color-picker xclip xdg-desktop-portal-hyprland-git xdg-utils xorg-xwayland zathura-pdf-mupdf zenity

View file

@ -1,16 +0,0 @@
# Devices
This directory encompasses every device's main configuration file.
## List of my Devices
| Name | Description |
| ----------- | ------------------------------------------------------------------------------------------------------- |
| `android` | My [Nix-On-Droid](https://github.com/nix-community/nix-on-droid) configuration for my OnePlus 9 Pro |
| `bbsteamie` | My wife's SteamDeck that has a pink case |
| `binto` | My desktop PC with a multi-monitor setup and an NVIDIA (cringe) 3070 |
| `cluster` | Two Lenovo mini PCs that make use of [NixOS-pcsd](https://github.com/matt1432/nixos-pcsd) to form a cluster |
| `homie` | My Lenovo mini PC that will serve as a Home-assistant server |
| `nos` | My custom built NAS |
| `servivi` | A gaming PC in a previous life, it is now used as a build farm and hosts game servers |
| `wim` | My 2-1 Lenovo Laptop that I use for uni |

View file

@ -1,56 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) attrValues concatStringsSep;
in {
imports = [./nix-on-droid.nix];
environment.variables.FLAKE = "/data/data/com.termux.nix/files/home/.nix";
terminal.font = "${
pkgs.nerd-fonts.jetbrains-mono
}/share/fonts/truetype/NerdFonts/JetBrainsMono/JetBrainsMonoNerdFontMono-Regular.ttf";
environment.packages = [
(pkgs.writeShellApplication {
name = "switch";
runtimeInputs = attrValues {
inherit
(pkgs)
coreutils
nix-output-monitor
nvd
;
};
text = ''
oldProfile=$(realpath /nix/var/nix/profiles/per-user/nix-on-droid/profile)
nix-on-droid ${concatStringsSep " " [
"switch"
"--flake ${config.environment.variables.FLAKE}"
"--builders ssh-ng://matt@100.64.0.7"
''"$@"''
"|&"
"nom"
]} &&
nvd diff "$oldProfile" "$(realpath /nix/var/nix/profiles/per-user/nix-on-droid/profile)"
'';
})
];
environment.etcBackupExtension = ".bak";
environment.motd = null;
home-manager.backupFileExtension = "hm-bak";
# Set your time zone.
time.timeZone = "America/Montreal";
# No touchy
system.stateVersion = "23.05";
}

View file

@ -1,84 +0,0 @@
{
config,
self,
...
}: {
imports = [
self.nixosModules.base-droid
{
roles.base = {
enable = true;
user = "nix-on-droid";
};
}
self.nixosModules.tmux
{programs.tmux.enableCustomConf = true;}
];
nix = {
# Edit nix.conf
extraOptions = ''
experimental-features = nix-command flakes
keep-outputs = true
keep-derivations = true
warn-dirty = false
'';
substituters = [
# Nix-community
"https://nix-community.cachix.org"
];
trustedPublicKeys = [
# Nix-community
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
# Global hm settings
home-manager.config = {
imports = [
self.homeManagerModules.neovim
{
programs.neovim = {
enable = true;
user = "nix-on-droid";
};
}
self.homeManagerModules.shell
{programs.bash.enable = true;}
{
programs.bash.sessionVariables = {
FLAKE = config.environment.variables.FLAKE;
};
programs.bash.shellAliases = {
# Make ping work on nix-on-droid
# https://github.com/nix-community/nix-on-droid/issues/185#issuecomment-1659294700
ping = "/android/system/bin/linker64 /android/system/bin/ping";
# SSH
# Desktop
pc = "ssh -t matt@100.64.0.6 'tmux -2u new -At phone'";
# NAS
nos = "ssh -t matt@100.64.0.4 'tmux -2u new -At phone'";
# Experimenting server
servivi = "ssh -t matt@100.64.0.7 'tmux -2u new -At phone'";
# Home-assistant
homie = "ssh -t matt@100.64.0.10 'tmux -2u new -At phone'";
# Cluster nodes
thingone = "ssh -t matt@100.64.0.8 'tmux -2u new -At phone'";
thingtwo = "ssh -t matt@100.64.0.9 'tmux -2u new -At phone'";
};
}
];
home.stateVersion = "23.05";
};
}

View file

@ -1,73 +0,0 @@
{
mainUser,
self,
...
}: {
# ------------------------------------------------
# Imports
# ------------------------------------------------
imports = [
./hardware-configuration.nix
./modules
self.nixosModules.base
self.nixosModules.kmscon
self.nixosModules.plymouth
self.nixosModules.server
];
# State Version: DO NOT CHANGE
system.stateVersion = "24.11";
# ------------------------------------------------
# User Settings
# ------------------------------------------------
users.users.${mainUser} = {
isNormalUser = true;
extraGroups = [
"wheel"
"adm"
];
};
networking = {
hostName = "bbsteamie";
networkmanager.enable = true;
};
time.timeZone = "America/Montreal";
# ------------------------------------------------
# `Self` Modules configuration
# ------------------------------------------------
roles.base = {
enable = true;
user = mainUser;
};
roles.server = {
user = mainUser;
sshd.enable = true;
};
boot.plymouth = {
enable = true;
theme = "bgrt";
};
services.kmscon.enable = true;
home-manager.users.${mainUser} = {
imports = [
self.homeManagerModules.shell
];
programs = {
bash = {
enable = true;
promptMainColor = "pink";
};
};
};
}

View file

@ -1,61 +0,0 @@
{
config,
jovian,
modulesPath,
...
}: {
nixpkgs.hostPlatform = "x86_64-linux";
nixpkgs.overlays = [jovian.overlays.default];
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
jovian.nixosModules.default
];
jovian = {
steamos.useSteamOSConfig = true;
devices.steamdeck = {
enable = true;
enableGyroDsuService = true;
};
hardware.has.amd.gpu = true;
};
boot = {
kernelModules = ["kvm-amd"];
initrd.availableKernelModules = ["nvme" "xhci_pci" "usbhid" "sdhci_pci"];
loader = {
efi.canTouchEfiVariables = true;
systemd-boot = {
enable = true;
configurationLimit = 30;
};
};
};
virtualisation.waydroid.enable = true;
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "ext4";
};
"/boot" = {
device = "/dev/disk/by-label/NIXBOOT";
fsType = "vfat";
options = ["fmask=0022" "dmask=0022"];
};
};
swapDevices = [
{
device = "/.swapfile";
size = 16 * 1024; # 16GB
}
];
hardware.cpu.amd.updateMicrocode = config.hardware.enableRedistributableFirmware;
}

View file

@ -1,5 +0,0 @@
{...}: {
imports = [
./desktop
];
}

View file

@ -1,40 +0,0 @@
{pkgs, ...}: let
defaultSession = "plasma";
in {
imports = [
(import ./session-switching.nix defaultSession)
(import ./steam.nix defaultSession)
];
services.desktopManager.plasma6.enable = true;
programs = {
kdeconnect.enable = true;
xwayland.enable = true;
};
# Flatpak support for Discover
services.flatpak.enable = true;
services.packagekit.enable = true;
# Wayland env vars
environment.variables = {
NIXOS_OZONE_WL = "1";
ELECTRON_OZONE_PLATFORM_HINT = "auto";
};
environment.systemPackages = builtins.attrValues {
inherit
(pkgs)
firefox
wl-clipboard
xclip
;
inherit
(pkgs.kdePackages)
discover
krfb
;
};
}

View file

@ -1,81 +0,0 @@
# How to install Palia Map and Overwolf on Linux
Dependencies:
- latest GE-Proton: https://github.com/GloriousEggroll/proton-ge-custom
- protontricks: https://github.com/Matoking/protontricks
- protonhax: https://github.com/jcnils/protonhax
## First Step: Install and run Palia at least once
## Second Step: Setup Palia WINEPREFIX with latest GE-Proton
1. Delete prefix at /home/"$USER"/.steam/steam/steamapps/compatdata/2707930/pfx
```bash
rm -r /home/"$USER"/.steam/steam/steamapps/compatdata/2707930/pfx
```
2. Launch Palia with GE-Proton by going to game properties -> Compatibility -> Force the use ... -> Select GE-Proton...
3. Install dotnet48 and other Windows deps to allow for Overwolf installation
```bash
# Force proton version of protontricks
export PROTON_VERSION="GE-Proton9-10"
protontricks 2707930 dotnet48
# If VC is needed
protontricks 2707930 vcrun2010
protontricks 2707930 vcrun2012
protontricks 2707930 vcrun2013
```
## Third Step: Install Overwolf
1. Get this older version that worked for me here: https://overwolf.en.uptodown.com/windows/download/4714215
2. Install it with protontricks. Follow the GUI installer as if you were on Windows
```bash
export PROTON_VERSION="GE-Proton9-10"
protontricks-launch --appid 2707930 Downloads/overwolf-0-195-0-18.exe
```
## Fourth Step: Install Palia Map
1. Get the installer from here: https://www.overwolf.com/app/Leon_Machens-Palia_Map
2. Install it with protontricks.
```bash
export PROTON_VERSION="GE-Proton9-10"
protontricks-launch --appid 2707930 Downloads/Palia\ Map\ -\ Installer.exe
```
3. An error should popup saying `Installation failed` or something along those lines.
Close it and wait. You should see the Overwolf overlay on the left side of your screen
with the Palia Map icon saying `Installing` and then when it's done: `Palia Map`.
If nothing happens, try rebooting your PC and retrying the above shell commands until it works.
## Final Step: Setting it up in Steam
1. Add this line to Palia launch options
```bash
protonhax init %command%
```
2. Add random non-steam game to your library and then edit its properties:
Change the name to `Palia Map` or anything you like
Change the target to this command:
```bash
protonhax run 2707930 "/home/$USER/.local/share/Steam/steamapps/compatdata/2707930/pfx/drive_c/users/steamuser/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Overwolf/Palia Map.lnk"
```

View file

@ -1,114 +0,0 @@
defaultSession: {
config,
lib,
mainUser,
pkgs,
...
}: {
config = let
inherit (lib) findFirst getExe mkForce;
switch-session = pkgs.writeShellApplication {
name = "switch-session";
text = ''
mkdir -p /etc/sddm.conf.d
cat <<EOF | tee /etc/sddm.conf.d/autologin.conf
[Autologin]
User=${mainUser}
Session=$1
Relogin=true
EOF
'';
};
gaming-mode = pkgs.writeShellScriptBin "gaming-mode" ''
sudo ${pkgs.systemd}/bin/systemctl start to-gaming-mode.service
'';
in {
services.displayManager.sddm = {
enable = true;
autoLogin.relogin = true;
wayland = {
enable = true;
compositorCommand = "kwin";
};
};
# Sets the default session at launch
systemd.services."set-session" = {
wantedBy = ["multi-user.target"];
before = ["display-manager.service"];
path = [switch-session];
script = ''
switch-session "${defaultSession}"
'';
};
# Allows switching to gaming mode
systemd.services."to-gaming-mode" = {
wantedBy = mkForce [];
path = [switch-session];
script = ''
switch-session "gamescope-wayland"
systemctl restart display-manager
sleep 10
switch-session "${defaultSession}"
'';
};
# Make it so we don't need root to switch to gaming mode
security.sudo.extraRules = [
{
users = [mainUser];
groups = [100];
commands = [
{
command = "${pkgs.systemd}/bin/systemctl start to-gaming-mode.service";
options = ["SETENV" "NOPASSWD"];
}
];
}
];
home-manager.users.${mainUser} = {
# Add desktop entry to make it GUI friendly
xdg.desktopEntries."Gaming Mode" = {
name = "Gaming Mode";
exec = getExe gaming-mode;
icon = "steam";
terminal = false;
type = "Application";
};
home.file."Desktop/Gaming Mode.desktop".source =
(
findFirst
(x: x.meta.name == "Gaming Mode.desktop") {}
config.home-manager.users.mariah.home.packages
)
+ "/share/applications/Gaming Mode.desktop";
# Fix remote control prompt showing up everytime
xdg.configFile = let
mkAutostart = name: flags: {
"autostart/${name}.desktop".text = "[Desktop Entry]\nType=Application\nExec=${name} ${flags}";
};
in (
# Needs xdg-desktop-portal-kde patch provided by `self.overlays.xdg-desktop-portal-kde`
{"plasmaremotedesktoprc".text = "[Sharing]\nUnattended=true";}
// (mkAutostart "krfb" "--nodialog %c")
// (mkAutostart "steam" "-silent %U")
);
};
};
# For accurate stack trace
_file = ./session-switching.nix;
}

View file

@ -1,75 +0,0 @@
defaultSession: {
config,
lib,
mainUser,
pkgs,
self,
...
}: {
config = {
# Normal Steam Stuff
programs.steam = {
enable = true;
protontricks.enable = true;
remotePlay.openFirewall = true;
extraCompatPackages = [
self.packages.${pkgs.system}.proton-ge-latest
];
};
# Jovian Steam settings
jovian.steam = {
# Steam > Settings > System > Enable Developer Mode
# Steam > Developer > CEF Remote Debugging
enable = true;
user = mainUser;
environment = {
STEAM_EXTRA_COMPAT_TOOLS_PATHS =
lib.makeSearchPathOutput
"steamcompattool"
""
config.programs.steam.extraCompatPackages;
};
desktopSession = defaultSession;
};
# Decky settings
jovian.decky-loader = {
enable = true;
user = mainUser;
stateDir = "/home/${mainUser}/.local/share/decky"; # Keep scoped to user
# https://github.com/Jovian-Experiments/Jovian-NixOS/blob/1171169117f63f1de9ef2ea36efd8dcf377c6d5a/modules/decky-loader.nix#L80-L84
extraPackages = builtins.attrValues {
inherit
(pkgs)
curl
unzip
util-linux
gnugrep
readline
procps
pciutils
libpulseaudio
;
};
};
# Misc Packages
environment.systemPackages = [
pkgs.steam-rom-manager
pkgs.r2modman
self.packages.${pkgs.system}.protonhax
# Ryujinx ACNH crashes on Vulkan
pkgs.ryujinx
];
};
# For accurate stack trace
_file = ./steam.nix;
}

View file

@ -1,94 +0,0 @@
{
mainUser,
self,
...
}: {
# ------------------------------------------------
# Imports
# ------------------------------------------------
imports = [
./hardware-configuration.nix
./modules
self.nixosModules.base
self.nixosModules.desktop
self.nixosModules.kmscon
self.nixosModules.server
];
# State Version: DO NOT CHANGE
system.stateVersion = "23.11";
# ------------------------------------------------
# User Settings
# ------------------------------------------------
users.users.${mainUser} = {
isNormalUser = true;
extraGroups = [
"wheel"
"input"
"uinput"
"adm"
"video"
"libvirtd"
"adbusers"
];
};
programs.adb.enable = true;
networking = {
hostName = "binto";
networkmanager.enable = true;
firewall.enable = false;
};
time.timeZone = "America/Montreal";
# ------------------------------------------------
# `Self` Modules configuration
# ------------------------------------------------
roles.base = {
enable = true;
user = mainUser;
};
roles.desktop = {
user = mainUser;
ags.enable = true;
mainMonitor = "desc:GIGA-BYTE TECHNOLOGY CO. LTD. G27QC 0x00000B1D";
displayManager.duplicateScreen = false;
fontSize = 12.5;
};
roles.server = {
user = mainUser;
tailscale.enable = true;
sshd.enable = true;
};
services.kmscon.enable = true;
home-manager.users.${mainUser} = {
imports = [
self.homeManagerModules.firefox
self.homeManagerModules.neovim
self.homeManagerModules.shell
];
programs = {
bash = {
enable = true;
promptMainColor = "purple";
};
neovim = {
enable = true;
user = mainUser;
};
};
};
}

View file

@ -1,117 +0,0 @@
{
config,
modulesPath,
pkgs,
...
}: {
nixpkgs.hostPlatform = "x86_64-linux";
imports = [(modulesPath + "/installer/scan/not-detected.nix")];
boot = {
kernelPackages = pkgs.linuxPackages_zen;
kernelParams = [
"amd_pstate=active"
# Remove these if I use plymouth module
"quiet"
"splash"
"boot.shell_on_fail"
"i915.fastboot=1"
"loglevel=3"
"rd.systemd.show_status=false"
"rd.udev.log_level=3"
"udev.log_priority=3"
];
kernelModules = ["kvm-amd"];
# Zenpower for ryzen cpu monitoring
extraModulePackages = builtins.attrValues {
inherit
(config.boot.kernelPackages)
v4l2loopback
zenpower
;
};
blacklistedKernelModules = ["k10temp"];
supportedFilesystems = ["ntfs"];
consoleLogLevel = 0;
initrd = {
verbose = false;
systemd.enable = true;
availableKernelModules = ["nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod"];
};
loader = {
efi.canTouchEfiVariables = true;
timeout = 0;
systemd-boot = {
enable = true;
consoleMode = "max";
configurationLimit = 30;
};
};
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "btrfs";
};
# sudo btrfs subvolume create /@swap
"/swap" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "btrfs";
# Idk why this is the subvol
options = ["subvol=@/@swap"];
};
"/boot" = {
device = "/dev/disk/by-label/NIXBOOT";
fsType = "vfat";
};
"/run/media/matt/Games" = {
device = "/dev/disk/by-uuid/da62f4ee-d4a6-4fdd-ab12-9c5e131c6f30";
fsType = "ext4";
};
};
swapDevices = [
{
device = "/swap/swapfile";
size = 16 * 1024;
}
];
zramSwap.enable = true;
hardware = {
cpu.amd.updateMicrocode = config.hardware.enableRedistributableFirmware;
uinput.enable = true;
};
virtualisation = {
libvirtd.enable = true;
spiceUSBRedirection.enable = true;
};
environment.systemPackages = builtins.attrValues {
inherit
(pkgs)
qemu
virtiofsd
;
};
nvidia = {
enable = true;
enableNvidiaSettings = true;
enableWayland = true;
enableCUDA = true;
};
}

View file

@ -1,6 +0,0 @@
{...}: {
imports = [
./gpu-replay.nix
./nix-gaming.nix
];
}

View file

@ -1,85 +0,0 @@
{
config,
lib,
mainUser,
pkgs,
self,
...
}: let
inherit (lib) concatStringsSep getExe removePrefix;
inherit (self.packages.${pkgs.system}) gpu-screen-recorder gsr-kms-server;
hyprPkgs = config.home-manager.users.${mainUser}.wayland.windowManager.hyprland.finalPackage;
cfgDesktop = config.roles.desktop;
in {
security.wrappers = {
gpu-screen-recorder = {
owner = "root";
group = "video";
capabilities = "cap_sys_nice+ep";
source = getExe gpu-screen-recorder;
};
gsr-kms-server = {
owner = "root";
group = "video";
capabilities = "cap_sys_admin+ep";
source = getExe gsr-kms-server;
};
};
home-manager.users.${mainUser} = {
home.packages = [
(pkgs.writeShellApplication {
name = "gpu-save-replay";
runtimeInputs = with pkgs; [procps];
text = ''
pkill --signal SIGUSR1 -f gpu-screen-recorder
'';
})
(pkgs.writeShellApplication {
name = "gsr-start";
runtimeInputs = [
pkgs.pulseaudio
pkgs.xorg.xrandr
hyprPkgs
];
text = ''
main="${removePrefix "desc:" cfgDesktop.mainMonitor}"
WINDOW=$(hyprctl -j monitors | jq '.[] |= (.description |= gsub(","; ""))' | jq -r ".[] | select(.description | test(\"$main\")) | .name")
# Fix fullscreen game resolution
xrandr --output "$WINDOW" --primary
gpu-screen-recorder ${concatStringsSep " " [
''-v no''
''-r 1200''
''-df yes''
''-o /home/matt/Videos/Replay''
# Audio settings
''-ac aac''
''-a "$(pactl get-default-sink).monitor"''
''-a "$(pactl get-default-source)"''
# Video settings
''-w "$WINDOW"''
''-f 60''
''-c mkv''
''-k hevc''
''-q very_high''
]}
'';
})
];
wayland.windowManager.hyprland.settings = {
bind = [",F8, exec, ags request 'save-replay'"];
};
};
}

View file

@ -1,38 +0,0 @@
{
nix-gaming,
pkgs,
self,
...
}: {
imports = [
nix-gaming.nixosModules.platformOptimizations
];
programs = {
steam = {
enable = true;
protontricks.enable = true;
remotePlay.openFirewall = true;
extraCompatPackages = [
self.packages.${pkgs.system}.proton-ge-latest
];
platformOptimizations.enable = true;
};
};
environment.systemPackages = [
(pkgs.lutris.override {
extraLibraries = pkgs: [
# List library dependencies here
];
extraPkgs = pkgs: [
# List extra packages available to lutris here
];
})
pkgs.r2modman
pkgs.ryujinx
];
}

View file

@ -1,91 +0,0 @@
deviceName: {
config,
mainUser,
self,
...
}: let
clusterIP = config.services.pcsd.virtualIps.caddy-vip.ip;
in {
# ------------------------------------------------
# Imports
# ------------------------------------------------
imports = [
./hardware-configuration.nix
./modules
self.nixosModules.base
self.nixosModules.kmscon
self.nixosModules.server
];
# State Version: DO NOT CHANGE
system.stateVersion = "24.05";
# ------------------------------------------------
# User Settings
# ------------------------------------------------
users.users.${mainUser} = {
isNormalUser = true;
extraGroups = [
"wheel"
"adm"
];
};
networking = {
hostName = deviceName;
resolvconf.enable = true;
nameservers = [
clusterIP
"1.0.0.1"
];
extraHosts = ''
10.0.0.244 thingone
10.0.0.159 thingtwo
'';
firewall.enable = false;
};
time.timeZone = "America/Montreal";
# ------------------------------------------------
# `Self` Modules configuration
# ------------------------------------------------
roles.base = {
enable = true;
user = mainUser;
};
roles.server = {
user = mainUser;
tailscale.enable = true;
sshd.enable = true;
};
services.kmscon.enable = true;
home-manager.users.${mainUser} = {
imports = [
self.homeManagerModules.neovim
self.homeManagerModules.shell
];
programs = {
bash = {
enable = true;
promptMainColor =
if deviceName == "thingone"
then "green"
else if deviceName == "thingtwo"
then "red"
else "purple";
};
neovim = {
enable = true;
user = mainUser;
};
};
};
}

View file

@ -1,59 +0,0 @@
{
config,
modulesPath,
...
}: {
nixpkgs.hostPlatform = "x86_64-linux";
imports = [(modulesPath + "/installer/scan/not-detected.nix")];
boot = {
loader = {
efi.canTouchEfiVariables = true;
timeout = 2;
systemd-boot = {
enable = true;
consoleMode = "max";
configurationLimit = 30;
};
};
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"usbhid"
"usb_storage"
"sd_mod"
];
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "btrfs";
};
# sudo btrfs subvolume create /@swap
"/swap" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "btrfs";
options = ["subvol=@swap"];
};
"/boot" = {
device = "/dev/disk/by-label/NIXBOOT";
fsType = "vfat";
};
};
swapDevices = [
{
device = "/swap/swapfile";
size = 16 * 1024;
}
];
zramSwap.enable = true;
hardware.cpu.intel.updateMicrocode = config.hardware.enableRedistributableFirmware;
}

View file

@ -1,24 +0,0 @@
{...}: {
services = {
blocky = {
enable = true;
settings = {
upstream = {
default = [
"127.0.0.1:5335"
"127.0.0.1:5335"
];
};
blocking = {
blackLists = {
ads = [
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
];
};
};
};
};
};
}

View file

@ -1,174 +0,0 @@
{
caddy-plugins,
config,
mainUser,
pkgs,
...
}: let
inherit (config.sops) secrets;
inherit (config.networking) hostName;
caddy = caddy-plugins.packages.${pkgs.system}.default;
in {
imports = [caddy-plugins.nixosModules.default];
# User stuff
environment.systemPackages = [caddy];
users.users.${mainUser}.extraGroups = ["caddy"];
boot.kernel.sysctl."net.ipv4.ip_nonlocal_bind" = 1;
systemd.services.caddy.serviceConfig = {
EnvironmentFile = secrets.caddy-cloudflare.path;
# For some reason the service
# doesn't shutdown normally
KillSignal = "SIGKILL";
RestartKillSignal = "SIGKILL";
};
services.caddy = {
enable = true;
enableReload = false;
package = caddy;
virtualHosts = let
clusterIP = config.services.pcsd.virtualIps.caddy-vip.ip;
nosIP = "10.0.0.121";
serviviIP = "10.0.0.249";
homieIP = "100.64.0.10";
tlsConf = ''
tls {
dns cloudflare {$CLOUDFLARE_API_TOKEN}
resolvers 1.0.0.1
}
'';
mkPublicReverseProxy = subdomain: ip: extraConf:
{
hostName = "${subdomain}.nelim.org";
reverseProxy = ip;
listenAddresses = [clusterIP];
extraConfig = tlsConf + (extraConf.extraConfig or "");
}
// (builtins.removeAttrs extraConf ["extraConfig"]);
in {
# Public
"Home-Assistant" = mkPublicReverseProxy "homie" "${homieIP}:8123" {};
"Vaultwarden" = mkPublicReverseProxy "vault" "${nosIP}:8781" {};
"Hauk" = mkPublicReverseProxy "hauk" "${nosIP}:3003" {};
"Headscale" = mkPublicReverseProxy "headscale" "${clusterIP}:8085" {};
"Jellyfin" = mkPublicReverseProxy "jelly" "${nosIP}:8096" {
subDirectories.jfa-go = {
subDirName = "accounts";
reverseProxy = "${nosIP}:8056";
};
};
"Jellyseer" = mkPublicReverseProxy "seerr" "${nosIP}:5055" {};
"Gameyfin" = mkPublicReverseProxy "games" "${nosIP}:8074" {};
"Forgejo" = mkPublicReverseProxy "git" "${nosIP}:3000" {};
"Nextcloud" = mkPublicReverseProxy "cloud" "${nosIP}:8042" {
extraConfig = ''
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
redir /.well-known/webfinger /index.php/.well-known/webfinger 301
redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 301
'';
};
"OnlyOffice" = mkPublicReverseProxy "office" "http://${nosIP}:8055" {};
"Immich" = mkPublicReverseProxy "photos" "${nosIP}:2283" {};
"Binary Cache" = mkPublicReverseProxy "cache" "${serviviIP}:5000" {};
"Apt Binary Cache" = mkPublicReverseProxy "cache-apt" "${homieIP}:5000" {};
# Private
"nelim.org" = {
serverAliases = ["*.nelim.org"];
extraConfig = tlsConf;
listenAddresses = [
(
if hostName == "thingone"
then "100.64.0.8"
else "100.64.0.9"
)
];
subDomains = {
esphome.reverseProxy = "${homieIP}:6052";
pr-tracker.reverseProxy = "${serviviIP}:3000";
pcsd = {
extraConfig = ''
reverse_proxy https://${clusterIP}:2224 {
transport http {
tls_insecure_skip_verify
}
}
'';
};
# Resume builder
resume.reverseProxy = "${nosIP}:3060";
resauth.reverseProxy = "${nosIP}:3100";
# FreshRSS & Co
bridge.reverseProxy = "${nosIP}:3006";
drss.reverseProxy = "${nosIP}:3007";
freshrss = {
subDomainName = "rss";
reverseProxy = "${nosIP}:2800";
};
wgui.reverseProxy = "${nosIP}:51821";
lan = {
reverseProxy = "${nosIP}:3020";
extraConfig = ''
redir /index.html /
'';
subDirectories = {
bazarr.reverseProxy = "${nosIP}:6767";
prowlarr.reverseProxy = "${nosIP}:9696";
radarr.reverseProxy = "${nosIP}:7878";
sabnzbd.reverseProxy = "${nosIP}:8382";
sonarr.reverseProxy = "${nosIP}:8989";
qbittorent = {
subDirName = "qbt";
experimental = true;
reverseProxy = "${nosIP}:8080";
};
vaultwarden = {
subDirName = "vault";
experimental = true;
reverseProxy = "${nosIP}:8780";
};
};
};
# Top secret Business
joal.extraConfig = ''
route {
rewrite * /joal/ui{uri}
reverse_proxy * ${nosIP}:5656
}
'';
joalws.extraConfig = ''
route {
reverse_proxy ${nosIP}:5656
}
'';
};
};
};
};
}

View file

@ -1,10 +0,0 @@
{...}: {
imports = [
./blocky.nix
./caddy.nix
./headscale
./nfs-client.nix
./pcsd.nix
./unbound.nix
];
}

View file

@ -1,338 +0,0 @@
# bash completion V2 for headscale -*- shell-script -*-
__headscale_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE-} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
__headscale_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
# This function calls the headscale program to obtain the completion
# results and the directive. It fills the 'out' and 'directive' vars.
__headscale_get_completion_results() {
local requestComp lastParam lastChar args
# Prepare the command to request completions for the program.
# Calling ${words[0]} instead of directly headscale allows handling aliases
args=("${words[@]:1}")
requestComp="${words[0]} __complete ${args[*]}"
lastParam=${words[$((${#words[@]}-1))]}
lastChar=${lastParam:$((${#lastParam}-1)):1}
__headscale_debug "lastParam ${lastParam}, lastChar ${lastChar}"
if [[ -z ${cur} && ${lastChar} != = ]]; then
# If the last parameter is complete (there is a space following it)
# We add an extra empty parameter so we can indicate this to the go method.
__headscale_debug "Adding extra empty parameter"
requestComp="${requestComp} ''"
fi
# When completing a flag with an = (e.g., headscale -n=<TAB>)
# bash focuses on the part after the =, so we need to remove
# the flag part from $cur
if [[ ${cur} == -*=* ]]; then
cur="${cur#*=}"
fi
__headscale_debug "Calling ${requestComp}"
# Use eval to handle any environment variables and such
out=$(eval "${requestComp}" 2>/dev/null)
# Extract the directive integer at the very end of the output following a colon (:)
directive=${out##*:}
# Remove the directive
out=${out%:*}
if [[ ${directive} == "${out}" ]]; then
# There is not directive specified
directive=0
fi
__headscale_debug "The completion directive is: ${directive}"
__headscale_debug "The completions are: ${out}"
}
__headscale_process_completion_results() {
local shellCompDirectiveError=1
local shellCompDirectiveNoSpace=2
local shellCompDirectiveNoFileComp=4
local shellCompDirectiveFilterFileExt=8
local shellCompDirectiveFilterDirs=16
local shellCompDirectiveKeepOrder=32
if (((directive & shellCompDirectiveError) != 0)); then
# Error code. No completion.
__headscale_debug "Received error from custom completion go code"
return
else
if (((directive & shellCompDirectiveNoSpace) != 0)); then
if [[ $(type -t compopt) == builtin ]]; then
__headscale_debug "Activating no space"
compopt -o nospace
else
__headscale_debug "No space directive not supported in this version of bash"
fi
fi
if (((directive & shellCompDirectiveKeepOrder) != 0)); then
if [[ $(type -t compopt) == builtin ]]; then
# no sort isn't supported for bash less than < 4.4
if [[ ${BASH_VERSINFO[0]} -lt 4 || ( ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 4 ) ]]; then
__headscale_debug "No sort directive not supported in this version of bash"
else
__headscale_debug "Activating keep order"
compopt -o nosort
fi
else
__headscale_debug "No sort directive not supported in this version of bash"
fi
fi
if (((directive & shellCompDirectiveNoFileComp) != 0)); then
if [[ $(type -t compopt) == builtin ]]; then
__headscale_debug "Activating no file completion"
compopt +o default
else
__headscale_debug "No file completion directive not supported in this version of bash"
fi
fi
fi
# Separate activeHelp from normal completions
local completions=()
local activeHelp=()
__headscale_extract_activeHelp
if (((directive & shellCompDirectiveFilterFileExt) != 0)); then
# File extension filtering
local fullFilter filter filteringCmd
# Do not use quotes around the $completions variable or else newline
# characters will be kept.
for filter in ${completions[*]}; do
fullFilter+="$filter|"
done
filteringCmd="_filedir $fullFilter"
__headscale_debug "File filtering command: $filteringCmd"
$filteringCmd
elif (((directive & shellCompDirectiveFilterDirs) != 0)); then
# File completion for directories only
local subdir
subdir=${completions[0]}
if [[ -n $subdir ]]; then
__headscale_debug "Listing directories in $subdir"
pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return
else
__headscale_debug "Listing directories in ."
_filedir -d
fi
else
__headscale_handle_completion_types
fi
__headscale_handle_special_char "$cur" :
__headscale_handle_special_char "$cur" =
# Print the activeHelp statements before we finish
if ((${#activeHelp[*]} != 0)); then
printf "\n";
printf "%s\n" "${activeHelp[@]}"
printf "\n"
# The prompt format is only available from bash 4.4.
# We test if it is available before using it.
if (x=${PS1@P}) 2> /dev/null; then
printf "%s" "${PS1@P}${COMP_LINE[@]}"
else
# Can't print the prompt. Just print the
# text the user had typed, it is workable enough.
printf "%s" "${COMP_LINE[@]}"
fi
fi
}
# Separate activeHelp lines from real completions.
# Fills the $activeHelp and $completions arrays.
__headscale_extract_activeHelp() {
local activeHelpMarker="_activeHelp_ "
local endIndex=${#activeHelpMarker}
while IFS='' read -r comp; do
if [[ ${comp:0:endIndex} == $activeHelpMarker ]]; then
comp=${comp:endIndex}
__headscale_debug "ActiveHelp found: $comp"
if [[ -n $comp ]]; then
activeHelp+=("$comp")
fi
else
# Not an activeHelp line but a normal completion
completions+=("$comp")
fi
done <<<"${out}"
}
__headscale_handle_completion_types() {
__headscale_debug "__headscale_handle_completion_types: COMP_TYPE is $COMP_TYPE"
case $COMP_TYPE in
37|42)
# Type: menu-complete/menu-complete-backward and insert-completions
# If the user requested inserting one completion at a time, or all
# completions at once on the command-line we must remove the descriptions.
# https://github.com/spf13/cobra/issues/1508
local tab=$'\t' comp
while IFS='' read -r comp; do
[[ -z $comp ]] && continue
# Strip any description
comp=${comp%%$tab*}
# Only consider the completions that match
if [[ $comp == "$cur"* ]]; then
COMPREPLY+=("$comp")
fi
done < <(printf "%s\n" "${completions[@]}")
;;
*)
# Type: complete (normal completion)
__headscale_handle_standard_completion_case
;;
esac
}
__headscale_handle_standard_completion_case() {
local tab=$'\t' comp
# Short circuit to optimize if we don't have descriptions
if [[ "${completions[*]}" != *$tab* ]]; then
IFS=$'\n' read -ra COMPREPLY -d '' < <(compgen -W "${completions[*]}" -- "$cur")
return 0
fi
local longest=0
local compline
# Look for the longest completion so that we can format things nicely
while IFS='' read -r compline; do
[[ -z $compline ]] && continue
# Strip any description before checking the length
comp=${compline%%$tab*}
# Only consider the completions that match
[[ $comp == "$cur"* ]] || continue
COMPREPLY+=("$compline")
if ((${#comp}>longest)); then
longest=${#comp}
fi
done < <(printf "%s\n" "${completions[@]}")
# If there is a single completion left, remove the description text
if ((${#COMPREPLY[*]} == 1)); then
__headscale_debug "COMPREPLY[0]: ${COMPREPLY[0]}"
comp="${COMPREPLY[0]%%$tab*}"
__headscale_debug "Removed description from single completion, which is now: ${comp}"
COMPREPLY[0]=$comp
else # Format the descriptions
__headscale_format_comp_descriptions $longest
fi
}
__headscale_handle_special_char()
{
local comp="$1"
local char=$2
if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then
local word=${comp%"${comp##*${char}}"}
local idx=${#COMPREPLY[*]}
while ((--idx >= 0)); do
COMPREPLY[idx]=${COMPREPLY[idx]#"$word"}
done
fi
}
__headscale_format_comp_descriptions()
{
local tab=$'\t'
local comp desc maxdesclength
local longest=$1
local i ci
for ci in ${!COMPREPLY[*]}; do
comp=${COMPREPLY[ci]}
# Properly format the description string which follows a tab character if there is one
if [[ "$comp" == *$tab* ]]; then
__headscale_debug "Original comp: $comp"
desc=${comp#*$tab}
comp=${comp%%$tab*}
# $COLUMNS stores the current shell width.
# Remove an extra 4 because we add 2 spaces and 2 parentheses.
maxdesclength=$(( COLUMNS - longest - 4 ))
# Make sure we can fit a description of at least 8 characters
# if we are to align the descriptions.
if ((maxdesclength > 8)); then
# Add the proper number of spaces to align the descriptions
for ((i = ${#comp} ; i < longest ; i++)); do
comp+=" "
done
else
# Don't pad the descriptions so we can fit more text after the completion
maxdesclength=$(( COLUMNS - ${#comp} - 4 ))
fi
# If there is enough space for any description text,
# truncate the descriptions that are too long for the shell width
if ((maxdesclength > 0)); then
if ((${#desc} > maxdesclength)); then
desc=${desc:0:$(( maxdesclength - 1 ))}
desc+="…"
fi
comp+=" ($desc)"
fi
COMPREPLY[ci]=$comp
__headscale_debug "Final comp: $comp"
fi
done
}
__start_headscale()
{
local cur prev words cword split
COMPREPLY=()
# Call _init_completion from the bash-completion package
# to prepare the arguments properly
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -n =: || return
else
__headscale_init_completion -n =: || return
fi
__headscale_debug
__headscale_debug "========= starting completion logic =========="
__headscale_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword"
# The user could have moved the cursor backwards on the command-line.
# We need to trigger completion from the $cword location, so we need
# to truncate the command-line ($words) up to the $cword location.
words=("${words[@]:0:$cword+1}")
__headscale_debug "Truncated words[*]: ${words[*]},"
local out directive
__headscale_get_completion_results
__headscale_process_completion_results
}
if [[ $(type -t compopt) = "builtin" ]]; then
complete -o default -F __start_headscale headscale
else
complete -o default -o nospace -F __start_headscale headscale
fi
# ex: ts=4 sw=4 et filetype=sh

View file

@ -1,70 +0,0 @@
{
config,
mainUser,
...
}: let
inherit (config.networking) hostName;
clusterIP = config.services.pcsd.virtualIps.caddy-vip.ip;
in {
users.users.${mainUser}.extraGroups = ["headscale"];
services.headscale = {
enable = true;
settings = {
server_url = "https://headscale.nelim.org";
listen_addr = "${clusterIP}:8085";
prefixes = {
v4 = "100.64.0.0/10";
v6 = "fd7a:115c:a1e0::/48";
};
metrics_listen_addr = "127.0.0.1:9090";
grpc_listen_addr = "0.0.0.0:50443";
grpc_allow_insecure = false;
disable_check_updates = true;
ephemeral_node_inactivity_timeout = "30m";
unix_socket = "/run/headscale/headscale.sock";
unix_socket_permission = "0770";
database = {
type = "sqlite";
sqlite.path = "/var/lib/headscale/db.sqlite";
};
private_key_path = "/var/lib/headscale/private.key";
noise.private_key_path = "/var/lib/headscale/noise_private.key";
dns = let
caddyIp =
if hostName == "thingone"
then "100.64.0.8"
else "100.64.0.9";
in {
magic_dns = false;
override_local_dns = true;
nameservers.global = [caddyIp];
};
log = {
format = "text";
level = "info";
};
derp = {
auto_update_enable = true;
update_frequency = "24h";
server = {
enabled = true;
stun_listen_addr = "${clusterIP}:3479";
private_key_path = "/var/lib/headscale/derp_server_private.key";
region_id = 995;
region_code = "mon";
region_name = "montreal";
};
};
};
};
}

View file

@ -1,32 +0,0 @@
{pkgs, ...}: {
# NFS client setup
services.rpcbind.enable = true;
boot.supportedFilesystems = ["nfs"];
environment.systemPackages = builtins.attrValues {
inherit (pkgs) nfs-utils;
};
systemd.mounts = let
host = "10.0.0.249";
in [
{
type = "nfs";
mountConfig = {
Options = "noatime";
};
what = "${host}:/caddy";
where = "/var/lib/caddy";
requiredBy = ["caddy.service"];
}
{
type = "nfs";
mountConfig = {
Options = "noatime";
};
what = "${host}:/headscale";
where = "/var/lib/headscale";
requiredBy = ["headscale.service"];
}
];
}

View file

@ -1,67 +0,0 @@
{
config,
pcsd,
...
}: let
inherit (config.sops) secrets;
in {
imports = [pcsd.nixosModules.default];
services.pcsd = {
enable = true;
enableBinaryCache = true;
enableWebUI = true;
clusterName = "thingies";
corosyncKeyFile = secrets.corosync.path;
clusterUserPasswordFile = secrets.pcs-pass.path;
virtualIps = {
"caddy-vip" = {
ip = "10.0.0.130";
interface = "eno1";
group = "caddy-grp";
};
};
systemdResources = {
"unbound" = {
enable = true;
group = "caddy-grp";
startAfter = ["caddy-vip"];
};
"blocky" = {
enable = true;
group = "caddy-grp";
startAfter = ["unbound"];
};
"headscale" = {
enable = true;
group = "caddy-grp";
startAfter = ["blocky"];
};
"caddy" = {
enable = true;
group = "caddy-grp";
startAfter = ["headscale"];
};
};
nodes = [
{
name = "thingone";
nodeid = 1;
ring_addrs = ["10.0.0.244"];
}
{
name = "thingtwo";
nodeid = 2;
ring_addrs = ["10.0.0.159"];
}
];
};
}

View file

@ -1,97 +0,0 @@
{
config,
lib,
mainUser,
...
}: let
inherit (lib) foldl isList mapAttrsToList mergeAttrsWithFunc remove unique;
mergeAttrsList = list:
foldl (mergeAttrsWithFunc (a: b:
if isList a && isList b
then unique (a ++ b)
else b)) {}
list;
inherit (config.networking) hostName;
serviviIP = "100.64.0.7";
caddyIp =
if hostName == "thingone"
then "100.64.0.8"
else "100.64.0.9";
in {
# https://github.com/MatthewVance/unbound-docker-rpi/issues/4#issuecomment-1001879602
boot.kernel.sysctl."net.core.rmem_max" = 1048576;
users.users.${mainUser}.extraGroups = ["unbound"];
services.unbound = {
enable = true;
enableRootTrustAnchor = true;
resolveLocalQueries = false;
settings = {
server = let
mkLocalEntry = domain: ip: {
local-zone = ["${domain} redirect"];
local-data = ["\"${domain} IN A ${ip}\""];
};
mkMinecraftEntry = domain: port: {
local-zone = ["${domain} transparent"];
local-data = [
"\"${domain} IN A ${serviviIP}\""
"\"_minecraft._tcp.${domain}. 180 IN SRV 0 0 ${toString port} ${domain}.\""
];
};
forceResolveEntry = domain: {
local-zone = ["${domain} always_transparent"];
};
publicApps = remove "nelim.org" (mapAttrsToList (n: v: v.hostName) config.services.caddy.virtualHosts);
in
mergeAttrsList (
[(mkLocalEntry "cache-apt.nelim.org" "100.64.0.10")]
++ (map forceResolveEntry publicApps)
++ [
(mkMinecraftEntry "mc.nelim.org" 25569)
(mkMinecraftEntry "mc2.nelim.org" 25560)
(mkMinecraftEntry "cv.nelim.org" 25566)
(mkLocalEntry "nelim.org" caddyIp)
{
interface = ["127.0.0.1"];
port = 5335;
do-ip4 = true;
do-ip6 = false;
prefer-ip6 = false;
do-udp = true;
do-tcp = true;
# Performance
prefetch = true;
num-threads = 1;
private-address = [
"172.16.0.0/12"
"10.0.0.0/8"
"100.64.0.0/8"
"fd00::/8"
"fe80::/10"
];
# Default stuff
harden-glue = true;
harden-dnssec-stripped = true;
use-caps-for-id = false;
edns-buffer-size = 1232;
so-rcvbuf = "1m";
}
]
);
};
};
}

View file

@ -1,77 +0,0 @@
{
mainUser,
self,
...
}: {
# ------------------------------------------------
# Imports
# ------------------------------------------------
imports = [
./hardware-configuration.nix
./modules
self.nixosModules.base
self.nixosModules.docker
self.nixosModules.kmscon
self.nixosModules.server
];
# State Version: DO NOT CHANGE
system.stateVersion = "24.11";
# ------------------------------------------------
# User Settings
# ------------------------------------------------
users.users.${mainUser} = {
isNormalUser = true;
extraGroups = [
"wheel"
"adm"
];
};
networking = {
hostName = "homie";
resolvconf.enable = true;
firewall.enable = false;
};
time.timeZone = "America/Montreal";
# ------------------------------------------------
# `Self` Modules configuration
# ------------------------------------------------
roles.base = {
enable = true;
user = mainUser;
};
roles.server = {
user = mainUser;
tailscale.enable = true;
sshd.enable = true;
};
khepri.enable = true;
services.kmscon.enable = true;
home-manager.users.${mainUser} = {
imports = [
self.homeManagerModules.neovim
self.homeManagerModules.shell
];
programs = {
bash = {
enable = true;
promptMainColor = "yellow";
};
neovim = {
enable = true;
user = mainUser;
};
};
};
}

View file

@ -1,61 +0,0 @@
{
config,
modulesPath,
...
}: {
nixpkgs.hostPlatform = "x86_64-linux";
imports = [(modulesPath + "/installer/scan/not-detected.nix")];
boot = {
loader = {
efi.canTouchEfiVariables = true;
timeout = 2;
systemd-boot = {
enable = true;
consoleMode = "max";
configurationLimit = 30;
};
};
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"nvme"
"usbhid"
"usb_storage"
"sd_mod"
];
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "btrfs";
};
# sudo btrfs subvolume create /@swap
"/swap" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "btrfs";
options = ["subvol=@swap"];
};
"/boot" = {
device = "/dev/disk/by-label/NIXBOOT";
fsType = "vfat";
options = ["fmask=0022" "dmask=0022"];
};
};
swapDevices = [
{
device = "/swap/swapfile";
size = 16 * 1024;
}
];
zramSwap.enable = true;
hardware.cpu.intel.updateMicrocode = config.hardware.enableRedistributableFirmware;
}

View file

@ -1,102 +0,0 @@
# Unfortunately I had some hardware issues but this does work
{
lib,
pkgs,
...
}: let
inherit (lib) mkForce getExe;
connectControllers = getExe (pkgs.writeShellApplication {
name = "connectControllers";
runtimeInputs = with pkgs; [gnugrep usbutils];
text = ''
set +o errexit
for dev in /sys/bus/usb/devices/*; do
vendor="$(cat "$dev/idVendor" 2>/dev/null)"
prod="$(cat "$dev/idProduct" 2>/dev/null)"
if [[ "$vendor" != "" && "$prod" != "" ]]; then
if [[ "$(lsusb -d "$vendor:$prod" | grep "Microsoft Corp. Xbox Controller")" != "" ]]; then
echo 0 > "$dev/authorized"
echo 1 > "$dev/authorized"
fi
fi
done
'';
});
hyprConf = pkgs.writeText "greetd-hypr-config" ''
cursor {
inactive_timeout = 1
}
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
}
decoration {
blur {
enabled = false
}
}
animations {
enabled = false
first_launch_animation = false
}
bind = SUPER, Q, exec, kitty
windowrule = fullscreen, ^(.*)$
exec-once = waydroid show-full-ui
exec-once = sleep 10; sudo ${connectControllers}
'';
user = "matt";
command = "Hyprland --config ${hyprConf}";
session = {inherit command user;};
in {
# Make it so we don't need root to connect controllers
security.sudo.extraRules = [
{
users = [user];
groups = [user];
commands = [
{
command = connectControllers;
options = ["SETENV" "NOPASSWD"];
}
];
}
];
# TODO: make the following declarative and also make the image declarative
# Add this to /var/lib/waydroid/waydroid.cfg for controller support
# persist.waydroid.udev = true
# persist.waydroid.uevent = true
virtualisation.waydroid.enable = true;
users.users."greeter" = {
home = "/var/lib/greeter";
};
programs.hyprland.enable = true;
services = {
greetd = {
enable = true;
settings = {
default_session = session;
initial_session = session;
};
};
pipewire.enable = mkForce false;
};
environment.systemPackages = [pkgs.kitty];
}

View file

@ -1,34 +0,0 @@
{
config,
pkgs,
...
}: let
inherit (builtins) attrValues;
inherit (config.sops) secrets;
nixFastBuild = pkgs.writeShellApplication {
name = "nixFastBuild";
runtimeInputs = attrValues {
inherit
(pkgs)
gnugrep
nix-fast-build
nix-output-monitor
;
};
text = ''
cd "$FLAKE/results" || return
nix-fast-build -f ..#nixFastChecks.aptDevices "$@"
'';
};
in {
services.nix-serve = {
enable = true;
secretKeyFile = secrets.apt-binary-cache-key.path;
};
environment.systemPackages = [pkgs.nix-fast-build nixFastBuild];
}

View file

@ -1,7 +0,0 @@
{...}: {
imports = [
./binary-cache.nix
./home-assistant
./music
];
}

View file

@ -1,83 +0,0 @@
{
pkgs,
self,
wakewords-src,
...
}: {
imports = [
self.nixosModules.esphome-plus
self.nixosModules.wyoming-plus
];
services = {
home-assistant = {
package = pkgs.home-assistant.override {
packageOverrides = final: prev: {
# HassTimer has way too many collisions with my custom timer sentences
home-assistant-intents = prev.home-assistant-intents.overrideAttrs (o: {
nativeBuildInputs = o.nativeBuildInputs ++ [pkgs.findutils];
postPatch = ''
find ./. -name "*Timer*" -delete
find ./. -name "*Start*" -delete
'';
});
};
};
customComponents = builtins.attrValues {
inherit
(self.scopedPackages.${pkgs.system}.hass-components)
extended-ollama-conversation # url is without subdirectory
ha-fallback-conversation
tuya-local
;
};
extraComponents = [
"esphome"
"ollama"
"wyoming"
"scrape"
];
config = {
assist_pipeline = {};
conversation = {};
media_source = {};
};
};
wyoming = {
piper.servers."en" = {
enable = true;
uri = "tcp://127.0.0.1:10200";
# see https://github.com/rhasspy/rhasspy3/blob/master/programs/tts/piper/script/download.py
voice = "en-us-ryan-low"; # using `hfc male (medium)` in GUI
speaker = 0;
};
openwakeword = {
enable = true;
uri = "tcp://127.0.0.1:10400";
threshold = 0.55;
vadThreshold = 0.50;
customModelsDirectories = ["${wakewords-src}/en/yo_homie"];
preloadModels = ["yo_homie"];
extraArgs = ["--debug"];
};
};
esphome = {
enable = true;
address = "100.64.0.10";
port = 6052;
};
};
# In case tailscale is down
boot.kernel.sysctl."net.ipv4.ip_nonlocal_bind" = 1;
}

View file

@ -1,78 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) getExe;
turnOnUE = pkgs.writeShellApplication {
name = "turnOnUE";
runtimeInputs = [config.hardware.bluetooth.package];
text = ''
cmd=0x0003
bt_device_addr=88:C6:26:93:4B:77
# This is the MAC address on the first line of `bluetootctl show`
# without the `:` and with `01` added at the end
bt_controller_addr=E848B8C8200001
exec gatttool -b $bt_device_addr --char-write-req --handle=$cmd --value=$bt_controller_addr
'';
};
in {
environment.systemPackages = [turnOnUE];
services.home-assistant = {
extraComponents = [
"mpd"
# BT components
"ibeacon"
"led_ble"
"kegtron"
"xiaomi_ble"
];
# Turn On the speaker automatically when openwakeword is used
config = {
shell_command.turn_on_ue = getExe turnOnUE;
script.turn_on_ue = {
alias = "Music - TurnOnUE";
description = "Script for turning on the UE Boom 2 speaker.";
icon = "mdi:music";
mode = "single";
sequence = [
{
alias = "Run shell command";
service = "shell_command.turn_on_ue";
}
];
};
automation = [
{
alias = "Turn On UE";
mode = "single";
trigger = [
{
platform = "state";
entity_id = "wake_word.openwakeword";
}
];
condition = [];
action = [
{
action = "shell_command.turn_on_ue";
metadata = {};
data = {};
}
];
}
];
};
};
}

View file

@ -1,118 +0,0 @@
{
pkgs,
self,
...
}: {
imports = [
./assist.nix
./bluetooth.nix
./firmware.nix
./frontend.nix
./netdaemon
./spotify
./timer
self.nixosModules.ha-plus
];
services.home-assistant = {
enable = true;
extraComponents = [
"androidtv"
"androidtv_remote"
"caldav"
"cast"
"holiday"
"isal"
"met"
"switchbot"
"upnp"
"yamaha_musiccast"
];
customComponents = builtins.attrValues {
inherit
(self.scopedPackages.${pkgs.system}.hass-components)
yamaha-soundbar
;
};
config = {
homeassistant = {
name = "Home";
unit_system = "metric";
currency = "CAD";
country = "CA";
time_zone = "America/Montreal";
external_url = "https://homie.nelim.org";
};
media_player = [
{
platform = "yamaha_soundbar";
host = "192.168.0.96";
name = "Living Room Speaker";
sources = {
HDMI = "TV";
};
}
];
# Proxy settings
http = {
server_host = "0.0.0.0";
trusted_proxies = ["100.64.0.8" "100.64.0.9"];
use_x_forwarded_for = true;
};
# `default_config` enables too much stuff. this is what I want from it
config = {};
dhcp = {};
history = {};
image_upload = {};
logbook = {};
mobile_app = {};
my = {};
sun = {};
zeroconf = {};
};
};
environment.systemPackages = [
(pkgs.writeShellApplication {
name = "yaml2nix";
runtimeInputs = with pkgs; [yj];
text = ''
input="$(yj < "$1")"
output="''${2:-""}"
nixCode="$(nix eval --expr "builtins.fromJSON '''$input'''" --impure | alejandra -q | sed 's/ = null;/ = {};/g')"
if [[ "$output" != "" ]]; then
echo "$nixCode" > "$output"
else
echo "$nixCode"
fi
'';
})
(pkgs.writeShellApplication {
name = "nix2yaml";
runtimeInputs = with pkgs; [remarshal];
text = ''
input="$1"
output="''${2:-""}"
yamlCode="$(nix eval --json --file "$input" | remarshal --if json --of yaml)"
if [[ "$output" != "" ]]; then
echo "$yamlCode" > "$output"
else
echo "$yamlCode"
fi
'';
})
];
}

View file

@ -1,26 +0,0 @@
# I use nix2yaml from ../default.nix to convert this to YAML and place it in the functions of extended_ollama_conversation
[
{
spec = {
name = "get_attributes";
description = "Get attributes of any home assistant entity";
parameters = {
type = "object";
properties = {
entity_id = {
type = "string";
description = "entity_id";
};
};
required = ["entity_id"];
};
};
function = {
type = "template";
value_template = "{{ states[entity_id] }}";
};
}
]

View file

@ -1,51 +0,0 @@
{%- set customize_glob_exposed_attributes = {
".*": {
"friendly_name": true,
"temperature": true,
"current_temperature": true,
"temperature_unit": true,
"brightness": true,
"humidity": true,
"unit_of_measurement": true,
"device_class": true,
"current_position": true,
"percentage": true,
"volume_level": true,
"media_title": true,
"media_artist": true,
"media_album_name": true,
},
} %}
{%- macro get_exposed_attributes(entity_id) -%}
{%- set ns = namespace(exposed_attributes = {}, result = {}) %}
{%- for pattern, attributes in customize_glob_exposed_attributes.items() -%}
{%- if entity_id | regex_match(pattern) -%}
{%- set ns.exposed_attributes = dict(ns.exposed_attributes, **attributes) -%}
{%- endif -%}
{%- endfor -%}
{%- for attribute_key, should_include in ns.exposed_attributes.items() -%}
{%- if should_include and state_attr(entity_id, attribute_key) != None -%}
{%- set temp = {attribute_key: state_attr(entity_id, attribute_key)} if should_include is boolean else {attribute_key: should_include} -%}
{%- set ns.result = dict(ns.result, **temp) -%}
{%- endif -%}
{%- endfor -%}
{%- set result = ns.result | to_json if ns.result!={} else None -%}
{{"'" + result + "'" if result != None else ''}}
{%- endmacro -%}
I want you to act as a personal assistant who is aware of my smart home.
You will truthfully answer in one sentence in everyday language.
Current Time: {{now()}}
Available Devices:
```csv
entity_id,name,state,aliases,attributes
{% for entity in exposed_entities -%}
{{ entity.entity_id }},{{ entity.name }},{{ entity.state }},{{entity.aliases | join('/')}},{{get_exposed_attributes(entity.entity_id)}}
{% endfor -%}
```
The current state of devices is provided in available devices.
Do not restate what user says.

View file

@ -1,383 +0,0 @@
{config, ...}: {
services.esphome = {
secretsFile = config.sops.secrets.esphome.path;
firmwareConfigs = {
# -------------------------------------------------------------
"AtomEcho" = {
# Device specific settings
api.encryption.key = "!secret api_key";
ota = [
{
platform = "esphome";
password = "!secret ota_pass";
}
];
wifi = {
ssid = "!secret wifi_ssid";
password = "!secret wifi_password";
manual_ip = {
static_ip = "192.168.0.92";
gateway = "192.168.0.1";
subnet = "255.255.255.0";
};
ap = {
ssid = "Esp1 Fallback Hotspot";
password = "!secret ap_fallback";
};
};
# Hardware Declaration
esphome = {
friendly_name = "M5Stack Atom Echo";
min_version = "2024.9.0";
name = "m5stack-atom-echo";
name_add_mac_suffix = true;
};
esp32 = {
board = "m5stack-atom";
framework.type = "esp-idf";
};
esp_adf = {};
button = [
{
id = "button_safe_mode";
name = "Safe Mode Boot";
platform = "safe_mode";
}
{
id = "factory_reset_btn";
name = "Factory reset";
platform = "factory_reset";
}
];
microphone = [
{
adc_type = "external";
i2s_din_pin = "GPIO23";
id = "echo_microphone";
pdm = true;
platform = "i2s_audio";
}
];
speaker = [
{
dac_type = "external";
i2s_dout_pin = "GPIO21"; # "GPIO22"; turn off speaker
id = "echo_speaker";
channel = "mono";
platform = "i2s_audio";
}
];
i2s_audio = [
{
id = "i2s_audio_bus";
i2s_bclk_pin = "GPIO19";
i2s_lrclk_pin = "GPIO33";
}
];
light = [
{
id = "led";
name = "None";
entity_category = "config";
chipset = "SK6812";
pin = "GPIO27";
platform = "esp32_rmt_led_strip";
default_transition_length = "0s";
disabled_by_default = false;
num_leds = 1;
rgb_order = "grb";
rmt_channel = 0;
effects = [
{
pulse = {
name = "Slow Pulse";
max_brightness = "100%";
min_brightness = "50%";
transition_length = "250ms";
update_interval = "250ms";
};
}
{
pulse = {
name = "Fast Pulse";
max_brightness = "100%";
min_brightness = "50%";
transition_length = "100ms";
update_interval = "100ms";
};
}
];
}
];
# Home-assistant buttons
switch = [
{
id = "use_wake_word";
name = "Use wake word";
entity_category = "config";
optimistic = true;
platform = "template";
restore_mode = "RESTORE_DEFAULT_ON";
on_turn_on = [
{lambda = "id(va).set_use_wake_word(true);";}
{
"if" = {
condition.not = ["voice_assistant.is_running"];
"then" = ["voice_assistant.start_continuous"];
};
}
{"script.execute" = "reset_led";}
];
on_turn_off = [
"voice_assistant.stop"
{lambda = "id(va).set_use_wake_word(false);";}
{"script.execute" = "reset_led";}
];
}
{
id = "use_listen_light";
name = "Use listen light";
entity_category = "config";
optimistic = true;
platform = "template";
restore_mode = "RESTORE_DEFAULT_ON";
on_turn_on = [{"script.execute" = "reset_led";}];
on_turn_off = [{"script.execute" = "reset_led";}];
}
];
binary_sensor = [
{
id = "echo_button";
name = "Button";
entity_category = "diagnostic";
disabled_by_default = false;
platform = "gpio";
pin = {
inverted = true;
number = "GPIO39";
};
on_multi_click = [
{
timing = ["ON for at least 50ms" "OFF for at least 50ms"];
"then" = [
{
"if" = {
condition = {"switch.is_off" = "use_wake_word";};
"then" = [
{
"if" = {
condition = "voice_assistant.is_running";
"then" = [
{"voice_assistant.stop" = {};}
{"script.execute" = "reset_led";}
];
"else" = [{"voice_assistant.start" = {};}];
};
}
];
"else" = [
"voice_assistant.stop"
{delay = "1s";}
{"script.execute" = "reset_led";}
{"script.wait" = "reset_led";}
{"voice_assistant.start_continuous" = {};}
];
};
}
];
}
{
timing = ["ON for at least 10s"];
"then" = [{"button.press" = "factory_reset_btn";}];
}
];
}
];
# Misc
logger = {};
external_components = [
{
source = "github://pr#5230";
components = ["esp_adf"];
refresh = "0s";
}
];
# Configs
script = [
{
id = "reset_led";
"then" = [
{
"if" = {
condition = [
{"switch.is_on" = "use_wake_word";}
{"switch.is_on" = "use_listen_light";}
];
"then" = [
{
"light.turn_on" = {
id = "led";
brightness = "60%";
effect = "none";
red = "100%";
green = "89%";
blue = "71%";
};
}
];
"else" = [{"light.turn_off" = "led";}];
};
}
];
}
];
voice_assistant = {
id = "va";
speaker = "echo_speaker";
microphone = "echo_microphone";
auto_gain = "31dBFS";
noise_suppression_level = 2;
vad_threshold = 3;
volume_multiplier = 2;
on_listening = [
{
"light.turn_on" = {
id = "led";
effect = "Slow Pulse";
green = "0%";
red = "0%";
blue = "100%";
};
}
];
on_stt_vad_end = [
{
"light.turn_on" = {
id = "led";
effect = "Fast Pulse";
red = "0%";
green = "0%";
blue = "100%";
};
}
];
on_tts_start = [
{
"light.turn_on" = {
id = "led";
brightness = "100%";
effect = "none";
red = "0%";
green = "0%";
blue = "100%";
};
}
];
# Play audio from bluetooth speaker
on_tts_end = [
{
"homeassistant.service" = {
service = "media_player.play_media";
data = {
entity_id = "media_player.music_player_daemon";
media_content_id = "!lambda \"return x;\"";
media_content_type = "music";
announce = "\"true\"";
};
};
}
];
on_end = [
{delay = "100ms";}
{wait_until.not."speaker.is_playing" = {};}
{"script.execute" = "reset_led";}
];
on_error = [
{
"light.turn_on" = {
id = "led";
brightness = "100%";
effect = "none";
red = "100%";
green = "0%";
blue = "0%";
};
}
{delay = "1s";}
{"script.execute" = "reset_led";}
];
on_client_connected = [
{
"if" = {
condition = {"switch.is_on" = "use_wake_word";};
"then" = [
{"voice_assistant.start_continuous" = {};}
{"script.execute" = "reset_led";}
];
};
}
];
on_client_disconnected = [
{
"if" = {
condition = {"switch.is_on" = "use_wake_word";};
"then" = [
{"voice_assistant.stop" = {};}
{"light.turn_off" = "led";}
];
};
}
];
};
};
# -------------------------------------------------------------
};
};
}

View file

@ -1,332 +0,0 @@
{
caule-themes-src,
dracul-ha-src,
material-rounded-theme-src,
lib,
pkgs,
self,
...
}: let
inherit (lib) singleton;
inherit (pkgs.writers) writeYAML;
in {
services.home-assistant = {
configFiles = {
"themes/caule.yaml".source = "${caule-themes-src}/themes/caule-themes-pack-1.yaml";
"themes/dracul-ha.yaml".source = "${dracul-ha-src}/themes/dracul-ha.yaml";
"themes/material_rounded.yaml".source = "${material-rounded-theme-src}/themes/material_rounded.yaml";
"www/sidebar-config.yaml".source = writeYAML "sidebar" {
id = "my-sidebar";
order = [
# Top
{
item = "overview";
order = 1;
}
{
match = "href";
item = "calendar";
order = 2;
}
{
match = "href";
item = "todo";
order = 3;
}
# Bottom
{
bottom = true;
item = "esphome";
order = 5;
}
{
bottom = true;
item = "logbook";
order = 7;
}
{
bottom = true;
icon = "mdi:tools";
item = "developer tools";
name = "Developer tools";
order = 9;
}
{
bottom = true;
item = "settings";
order = 11;
}
# Hidden
{
hide = true;
item = "map";
}
{
hide = true;
item = "energy";
}
{
hide = true;
item = "history";
}
{
hide = true;
item = "media";
}
];
};
};
customComponents = builtins.attrValues {
inherit
(self.scopedPackages.${pkgs.system}.hass-components)
material-symbols
;
};
customLovelaceModules = builtins.attrValues {
inherit
(pkgs.home-assistant-custom-lovelace-modules)
card-mod
universal-remote-card
;
inherit
(self.scopedPackages.${pkgs.system}.lovelace-components)
custom-sidebar
;
};
config.frontend = {
themes = "!include_dir_merge_named themes";
extra_module_url = map (p: "/local/nixos-lovelace-modules/${p}.js") [
"card-mod"
"custom-sidebar-yaml"
];
};
config.template = [
{
sensor = singleton {
name = "Material Rounded Base Color Matt";
unique_id = "material_rounded_base_color_matt";
state = ''{{ states("sensor.pixel_8_accent_color") }}'';
};
}
];
lovelaceConfig = {
title = "Our House";
# I don't want multiple views
views = singleton {
path = "home";
title = "Home";
cards = [
{
type = "entities";
entities = [
"switch.smartplug1"
"switch.smartplug3"
];
}
{
type = "entities";
entities = [
"timer.assist_timer1"
"timer.assist_timer2"
"timer.assist_timer3"
];
}
{
type = "custom:android-tv-card";
visibility = singleton {
condition = "state";
entity = "remote.onn_4k_streaming_box";
state_not = ["unavailable" "unknown"];
};
media_player_id = "media_player.living_room_speaker";
keyboard_id = "remote.android_tv_192_168_0_106";
remote_id = "remote.onn_4k_streaming_box";
rows = [
"navigation_buttons"
[null "slider" null]
[null]
["jellyfin" "home" "back" "keyboard"]
[null]
];
custom_actions = [
{
name = "center";
type = "button";
icon = "mdi:checkbox-blank-circle";
styles = ''
:host {
--icon-color: rgb(94, 94, 94);
--size: 200px;
background: rgb(31, 31, 31);
border-radius: 200px;
margin: -70px;
padding: 70px;
}
'';
tap_action = {
action = "key";
key = "DPAD_CENTER";
};
}
{
name = "up";
type = "button";
icon = "mdi:chevron-up";
styles = ''
:host {
--icon-color: rgb(197, 199, 197);
z-index: 2;
top: 25px;
height: 90px;
width: 300px;
}
'';
hold_action = {action = "repeat";};
tap_action = {
action = "key";
key = "DPAD_UP";
};
}
{
name = "down";
type = "button";
icon = "mdi:chevron-down";
styles = ''
:host {
--icon-color: rgb(197, 199, 197);
z-index: 2;
bottom: 25px;
height: 90px;
width: 300px;
}
'';
hold_action = {action = "repeat";};
tap_action = {
action = "key";
key = "DPAD_DOWN";
};
}
{
name = "left";
type = "button";
icon = "mdi:chevron-left";
styles = ''
:host {
--icon-color: rgb(197, 199, 197);
z-index: 2;
left: 30px;
height: 170px;
width: 90px;
}
'';
hold_action = {action = "repeat";};
tap_action = {
action = "key";
key = "DPAD_LEFT";
};
}
{
name = "right";
type = "button";
icon = "mdi:chevron-right";
styles = ''
:host {
--icon-color: rgb(197, 199, 197);
z-index: 2;
right: 30px;
height: 170px;
width: 90px;
}
'';
hold_action = {action = "repeat";};
tap_action = {
action = "key";
key = "DPAD_RIGHT";
};
}
{
name = "slider";
type = "slider";
icon = "mdi:volume-high";
range = [0 1];
step = 0.01;
tap_action = {
action = "perform-action";
perform_action = "media_player.volume_set";
data = {
volume_level = "{{ value | float }}";
};
};
value_attribute = "volume_level";
}
];
styles = ''
#row-1 {
justify-content: center;
}
#row-2 {
justify-content: center;
}
#row-3 {
justify-content: center;
}
'';
}
];
};
};
config.lovelace.dashboards = {
esphome-dash = {
title = "ESPHome";
icon = "mdi:car-esp";
mode = "yaml";
show_in_sidebar = true;
require_admin = true;
filename = writeYAML "esphome.yaml" {
strategy = {
type = "iframe";
url = "https://esphome.nelim.org";
};
};
};
};
};
}

View file

@ -1,360 +0,0 @@
root = true
# All files
[*]
indent_style = space
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
tab_width = 4
# New line preferences
end_of_line = lf
insert_final_newline = true
#### .NET Coding Conventions ####
[*.{cs,vb}]
# Organize usings
dotnet_separate_import_directive_groups = true
dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:warning
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
#### C# Coding Conventions ####
[*.cs]
# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
[*.{cs,vb}]
# Naming rules
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.properties_should_be_pascalcase.severity = none
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
# Symbol specifications
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interfaces.required_modifiers =
dotnet_naming_symbols.enums.applicable_kinds = enum
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.enums.required_modifiers =
dotnet_naming_symbols.events.applicable_kinds = event
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.events.required_modifiers =
dotnet_naming_symbols.methods.applicable_kinds = method
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.methods.required_modifiers =
dotnet_naming_symbols.properties.applicable_kinds = property
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.properties.required_modifiers =
dotnet_naming_symbols.public_fields.applicable_kinds = field
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_fields.required_modifiers =
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_fields.required_modifiers =
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_static_fields.required_modifiers = static
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters.required_modifiers =
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
dotnet_naming_symbols.local_variables.applicable_kinds = local
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
dotnet_naming_symbols.local_variables.required_modifiers =
dotnet_naming_symbols.local_constants.applicable_kinds = local
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
dotnet_naming_symbols.local_constants.required_modifiers = const
dotnet_naming_symbols.parameters.applicable_kinds = parameter
dotnet_naming_symbols.parameters.applicable_accessibilities = *
dotnet_naming_symbols.parameters.required_modifiers =
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_constant_fields.required_modifiers = const
dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
dotnet_naming_symbols.local_functions.required_modifiers =
# Naming styles
dotnet_naming_style.pascalcase.required_prefix =
dotnet_naming_style.pascalcase.required_suffix =
dotnet_naming_style.pascalcase.word_separator =
dotnet_naming_style.pascalcase.capitalization = pascal_case
dotnet_naming_style.ipascalcase.required_prefix = I
dotnet_naming_style.ipascalcase.required_suffix =
dotnet_naming_style.ipascalcase.word_separator =
dotnet_naming_style.ipascalcase.capitalization = pascal_case
dotnet_naming_style.tpascalcase.required_prefix = T
dotnet_naming_style.tpascalcase.required_suffix =
dotnet_naming_style.tpascalcase.word_separator =
dotnet_naming_style.tpascalcase.capitalization = pascal_case
dotnet_naming_style._camelcase.required_prefix = _
dotnet_naming_style._camelcase.required_suffix =
dotnet_naming_style._camelcase.word_separator =
dotnet_naming_style._camelcase.capitalization = camel_case
dotnet_naming_style.camelcase.required_prefix =
dotnet_naming_style.camelcase.required_suffix =
dotnet_naming_style.camelcase.word_separator =
dotnet_naming_style.camelcase.capitalization = camel_case
dotnet_naming_style.s_camelcase.required_prefix = s_
dotnet_naming_style.s_camelcase.required_suffix =
dotnet_naming_style.s_camelcase.word_separator =
dotnet_naming_style.s_camelcase.capitalization = camel_case

View file

@ -1,2 +0,0 @@
use flake "$FLAKE#netdaemon"
cp -rf ./HomeAssistantGenerated ./HomeAssistantGenerated.cs

View file

@ -1,4 +0,0 @@
obj
bin
NetDaemonCodegen
HomeAssistantGenerated.cs

View file

@ -1,50 +0,0 @@
using System;
using HomeAssistantGenerated;
using NetDaemon.AppModel;
using NetDaemon.HassModel;
using NetDaemon.HassModel.Integration;
using NetDaemonConfig.Apps.Spotify.Types;
namespace NetDaemonConfig.Apps.Spotify.PauseUnpause
{
public record PauseUnpauseData(bool pause);
[NetDaemonApp]
public class PlaySong
{
public PlaySong(IHaContext ha, Services services)
{
ha.RegisterServiceCallBack<PauseUnpauseData>(
"spotify_pause_unpause",
(e) =>
{
try
{
if (e.pause)
{
services.Spotifyplus.PlayerMediaPause(
entityId: Globals.DefaultEntityId,
deviceId: Globals.DefaultDevId);
}
else
{
services.Spotifyplus.PlayerMediaResume(
entityId: Globals.DefaultEntityId,
deviceId: Globals.DefaultDevId);
}
}
catch (Exception error)
{
services.Notify.PersistentNotification(
message: error.Message,
title: "Erreur Spotify");
}
}
);
}
}
}

View file

@ -1,120 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.Json;
using FuzzySharp;
using FuzzySharp.Extractor;
using HomeAssistantGenerated;
using NetDaemon.AppModel;
using NetDaemon.HassModel;
using NetDaemon.HassModel.Integration;
using NetDaemonConfig.Apps.Spotify.Types;
namespace NetDaemonConfig.Apps.Spotify.PlayAlbum
{
public record PlayAlbumData(string? artist, string? album);
[NetDaemonApp]
public class PlayAlbum
{
private readonly CultureInfo _cultureInfo = new("fr-CA", false);
// Snake-case json options
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public PlayAlbum(IHaContext ha, Services services)
{
ha.RegisterServiceCallBack<PlayAlbumData>(
"spotify_play_album",
async (e) =>
{
try
{
string uri;
if (e.artist is not null)
{
SpotifyplusSearchArtistsResponse? artistResult = (
await services.Spotifyplus.SearchArtistsAsync(
criteria: e?.artist ??
throw new TargetException($"The artist {e?.artist} could not be found."),
limitTotal: 1,
entityId: Globals.DefaultEntityId,
// My Defaults
market: "CA",
includeExternal: "audio"
)
).Value.Deserialize<SpotifyplusSearchArtistsResponse>(_jsonOptions);
string artistId = artistResult?.Result?.Items?[0]?.Id ??
throw new TargetException($"The artist {e?.artist} could not be found.");
SpotifyPlusGetArtistAlbumsResponse? result = (
await services.Spotifyplus.GetArtistAlbumsAsync(
artistId: artistId,
entityId: Globals.DefaultEntityId,
market: "CA"
)
).Value.Deserialize<SpotifyPlusGetArtistAlbumsResponse>(_jsonOptions);
List<ArtistAlbumItem> albums = result?.Result?.Items ??
throw new TargetException($"No albums found for artist {e.artist}");
ExtractedResult<ArtistAlbumItem> match = Process.ExtractOne(
new ArtistAlbumItem { Name = e.album?.ToLower(_cultureInfo) },
albums,
new Func<ArtistAlbumItem, string>((item) =>
(item.Name ?? "").ToLower(_cultureInfo))
);
uri = match.Value?.Uri ??
throw new TargetException($"No matches found for album {e.album}");
}
else
{
SpotifyplusSearchAlbumsResponse? result = (
await services.Spotifyplus.SearchAlbumsAsync(
criteria: $"{e?.album}",
limitTotal: 1,
entityId: Globals.DefaultEntityId,
// My Defaults
market: "CA",
includeExternal: "audio"
)
).Value.Deserialize<SpotifyplusSearchAlbumsResponse>(_jsonOptions);
uri = result?.Result?.Items?[0]?.Uri ??
throw new TargetException(
$"The album {e?.album}{(e?.artist is null ? "" : $" by {e?.artist}")} could not be found."
);
}
services.Spotifyplus.PlayerMediaPlayContext(
contextUri: uri,
entityId: Globals.DefaultEntityId,
deviceId: Globals.DefaultDevId,
// My Defaults
positionMs: 0,
delay: 0.50
);
}
catch (Exception error)
{
services.Notify.PersistentNotification(
message: error.Message,
title: "Erreur Spotify");
}
}
);
}
}
}

View file

@ -1,68 +0,0 @@
using System;
using System.Reflection;
using System.Text.Json;
using HomeAssistantGenerated;
using NetDaemon.AppModel;
using NetDaemon.HassModel;
using NetDaemon.HassModel.Integration;
using NetDaemonConfig.Apps.Spotify.Types;
namespace NetDaemonConfig.Apps.Spotify.PlayArtist
{
public record PlayArtistData(string? artist);
[NetDaemonApp]
public class PlayArtist
{
// Snake-case json options
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public PlayArtist(IHaContext ha, Services services)
{
ha.RegisterServiceCallBack<PlayArtistData>(
"spotify_play_artist",
async (e) =>
{
try
{
SpotifyplusSearchArtistsResponse? result = (
await services.Spotifyplus.SearchArtistsAsync(
criteria: e?.artist ?? throw new TargetException($"The artist {e?.artist} could not be found."),
limitTotal: 1,
entityId: Globals.DefaultEntityId,
// My Defaults
market: "CA",
includeExternal: "audio"
)
).Value.Deserialize<SpotifyplusSearchArtistsResponse>(_jsonOptions);
string uri = result?.Result?.Items?[0]?.Uri ??
throw new TargetException($"The artist {e?.artist} could not be found.");
services.Spotifyplus.PlayerMediaPlayContext(
contextUri: uri,
entityId: Globals.DefaultEntityId,
deviceId: Globals.DefaultDevId,
// My Defaults
positionMs: 0,
delay: 0.50
);
}
catch (Exception error)
{
services.Notify.PersistentNotification(
message: error.Message,
title: "Erreur Spotify");
}
}
);
}
}
}

View file

@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.Json;
using FuzzySharp;
using FuzzySharp.Extractor;
using HomeAssistantGenerated;
using NetDaemon.AppModel;
using NetDaemon.HassModel;
using NetDaemon.HassModel.Integration;
using NetDaemonConfig.Apps.Spotify.Types;
namespace NetDaemonConfig.Apps.Spotify.PlayPlaylist
{
public record PlayPlaylistData(string? playlist);
[NetDaemonApp]
public class PlayPlaylist
{
private readonly CultureInfo _cultureInfo = new("fr-CA", false);
// Snake-case json options
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public PlayPlaylist(IHaContext ha, Services services)
{
ha.RegisterServiceCallBack<PlayPlaylistData>(
"spotify_play_playlist",
async (e) =>
{
try
{
string query = e?.playlist ?? throw new TargetException("Query not found.");
SpotifyplusPlaylistResponse? result = (
await services.Spotifyplus.GetPlaylistFavoritesAsync(
limitTotal: 200,
sortResult: true,
entityId: Globals.DefaultEntityId
)
).Value.Deserialize<SpotifyplusPlaylistResponse>(_jsonOptions);
List<PlaylistsItem> myPlaylists = result?.Result?.Items ??
throw new TargetException($"No playlists found for query {query}");
ExtractedResult<PlaylistsItem> match = Process.ExtractOne(
new PlaylistsItem { Name = query.ToLower(_cultureInfo) },
myPlaylists,
new Func<PlaylistsItem, string>((item) => (item.Name ?? "").ToLower(_cultureInfo))
);
string uri = match.Value?.Uri ?? throw new TargetException($"No matches found for query {query}");
// We search outside the user's playlists if the score is too low
if (match.Score < 85)
{
SpotifyplusPlaylistResponse? otherResult = (
await services.Spotifyplus.SearchPlaylistsAsync(
criteria: query,
limitTotal: 1,
entityId: Globals.DefaultEntityId,
// My Defaults
market: "CA",
includeExternal: "audio"
)
).Value.Deserialize<SpotifyplusPlaylistResponse>(_jsonOptions);
string potentialUri = otherResult?.Result?.Items?[0]?.Uri ??
throw new TargetException($"No public matches found for query {query}");
uri = potentialUri;
}
services.Spotifyplus.PlayerMediaPlayContext(
contextUri: uri,
entityId: Globals.DefaultEntityId,
deviceId: Globals.DefaultDevId,
// My Defaults
positionMs: 0,
delay: 0.50
);
}
catch (Exception error)
{
services.Notify.PersistentNotification(
message: error.Message,
title: "Erreur Spotify");
}
}
);
}
}
}

View file

@ -1,69 +0,0 @@
using System;
using System.Reflection;
using System.Text.Json;
using HomeAssistantGenerated;
using NetDaemon.AppModel;
using NetDaemon.HassModel;
using NetDaemon.HassModel.Integration;
using NetDaemonConfig.Apps.Spotify.Types;
namespace NetDaemonConfig.Apps.Spotify.PlaySong
{
public record PlaySongData(string? artist, string? song);
[NetDaemonApp]
public class PlaySong
{
// Snake-case json options
private readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public PlaySong(IHaContext ha, Services services)
{
ha.RegisterServiceCallBack<PlaySongData>(
"spotify_play_song",
async (e) =>
{
try
{
SpotifyplusSearchTracksResponse? result = (
await services.Spotifyplus.SearchTracksAsync(
criteria: $"{e?.artist} {e?.song}",
limitTotal: 1,
entityId: Globals.DefaultEntityId,
// My Defaults
market: "CA",
includeExternal: "audio"
)
).Value.Deserialize<SpotifyplusSearchTracksResponse>(_jsonOptions);
string uri = result?.Result?.Items?[0]?.Uri ?? throw new TargetException(
$"The song {e?.song}{(e?.artist is null ? "" : $" by {e?.artist}")} could not be found."
);
services.Spotifyplus.PlayerMediaPlayTracks(
uris: uri,
entityId: Globals.DefaultEntityId,
deviceId: Globals.DefaultDevId,
// My Defaults
positionMs: 0,
delay: 0.50
);
}
catch (Exception error)
{
services.Notify.PersistentNotification(
message: error.Message,
title: "Erreur Spotify");
}
}
);
}
}
}

View file

@ -1,42 +0,0 @@
using System.Collections.Generic;
namespace NetDaemonConfig.Apps.Spotify.Types
{
public record ArtistAlbumItem
{
public string? AlbumType { get; set; }
public List<Artist>? Artists { get; set; }
public List<object>? AvailableMarkets { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? ImageUrl { get; set; }
public List<Image>? Images { get; set; }
public string? Name { get; set; }
public string? ReleaseDate { get; set; }
public string? ReleaseDatePrecision { get; set; }
public Restrictions? Restrictions { get; set; }
public int? TotalTracks { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record ArtistAlbumResult
{
public double? DateLastRefreshed { get; set; }
public string? Href { get; set; }
public int? Limit { get; set; }
public object? Next { get; set; }
public int? Offset { get; set; }
public object? Previous { get; set; }
public int? Total { get; set; }
public List<ArtistAlbumItem>? Items { get; set; }
}
public record SpotifyPlusGetArtistAlbumsResponse
{
public UserProfile? UserProfile { get; set; }
public ArtistAlbumResult? Result { get; set; }
}
}

View file

@ -1,102 +0,0 @@
using System.Collections.Generic;
namespace NetDaemonConfig.Apps.Spotify.Types
{
public static class Globals
{
public const string DefaultDevId = "homie";
public const string DefaultEntityId = "media_player.spotifyplus";
}
// https://jsonformatter.org/yaml-to-json
// https://json2csharp.com
// https://github.com/thlucas1/homeassistantcomponent_spotifyplus/blob/master/custom_components/spotifyplus/services.yaml
public record Restrictions { }
public record UserProfile
{
public string? Country { get; set; }
public string? DisplayName { get; set; }
public string? Email { get; set; }
public string? Id { get; set; }
public string? Product { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record ExternalUrls
{
public string? Spotify { get; set; }
}
public record Followers
{
public string? Href { get; set; }
public int? Total { get; set; }
}
public record Image
{
public string? Url { get; set; }
public int? Height { get; set; }
public int? Width { get; set; }
}
public record Owner
{
public string? DisplayName { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public Followers? Followers { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record Tracks
{
public string? Href { get; set; }
public int? Total { get; set; }
}
public record Artist
{
public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? Name { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record ExternalIds
{
public object? Ean { get; set; }
public string? Isrc { get; set; }
public object? Upc { get; set; }
}
public record Album
{
public string? AlbumType { get; set; }
public List<Artist>? Artists { get; set; }
public List<string>? AvailableMarkets { get; set; }
public List<object>? Copyrights { get; set; }
public ExternalIds? ExternalIds { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public List<object>? Genres { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? ImageUrl { get; set; }
public List<Image>? Images { get; set; }
public object? Label { get; set; }
public string? Name { get; set; }
public object? Popularity { get; set; }
public string? ReleaseDate { get; set; }
public string? ReleaseDatePrecision { get; set; }
public Restrictions? Restrictions { get; set; }
public int? TotalTracks { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
}

View file

@ -1,40 +0,0 @@
using System.Collections.Generic;
namespace NetDaemonConfig.Apps.Spotify.Types
{
public record PlaylistsItem
{
public bool? Collaborative { get; set; }
public string? Description { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? ImageUrl { get; set; }
public List<Image>? Images { get; set; }
public string? Name { get; set; }
public Owner? Owner { get; set; }
public bool? Public { get; set; }
public string? SnapshotId { get; set; }
public Tracks? Tracks { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record PlaylistsResult
{
public string? Href { get; set; }
public int? Limit { get; set; }
public object? Next { get; set; }
public int? Offset { get; set; }
public object? Previous { get; set; }
public int? Total { get; set; }
public List<PlaylistsItem>? Items { get; set; }
}
public record SpotifyplusPlaylistResponse
{
public UserProfile? UserProfile { get; set; }
public PlaylistsResult? Result { get; set; }
}
}

View file

@ -1,41 +0,0 @@
using System.Collections.Generic;
namespace NetDaemonConfig.Apps.Spotify.Types
{
public record AlbumItem
{
public string? AlbumType { get; set; }
public List<Artist>? Artists { get; set; }
public List<string>? AvailableMarkets { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? ImageUrl { get; set; }
public List<Image>? Images { get; set; }
public string? Name { get; set; }
public string? ReleaseDate { get; set; }
public string? ReleaseDatePrecision { get; set; }
public Restrictions? Restrictions { get; set; }
public int? TotalTracks { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record AlbumResult
{
public string? Href { get; set; }
public int? Limit { get; set; }
public string? Next { get; set; }
public int? Offset { get; set; }
public object? Previous { get; set; }
public int? Total { get; set; }
public List<AlbumItem>? Items { get; set; }
}
public record SpotifyplusSearchAlbumsResponse
{
public UserProfile? UserProfile { get; set; }
public AlbumResult? Result { get; set; }
}
}

View file

@ -1,37 +0,0 @@
using System.Collections.Generic;
namespace NetDaemonConfig.Apps.Spotify.Types
{
public record ArtistItem
{
public ExternalUrls? ExternalUrls { get; init; }
public Followers? Followers { get; init; }
public List<string>? Genres { get; init; }
public string? Href { get; init; }
public string? Id { get; init; }
public string? ImageUrl { get; init; }
public List<Image>? Images { get; init; }
public string? Name { get; init; }
public int? Popularity { get; init; }
public string? Type { get; init; }
public string? Uri { get; init; }
}
public record ArtistResult
{
public string? Href { get; init; }
public int? Limit { get; init; }
public string? Next { get; init; }
public int? Offset { get; set; }
public object? Previous { get; init; }
public int? Total { get; init; }
public List<ArtistItem>? Items { get; init; }
}
public record SpotifyplusSearchArtistsResponse
{
public UserProfile? UserProfile { get; init; }
public ArtistResult? Result { get; init; }
}
}

View file

@ -1,46 +0,0 @@
using System.Collections.Generic;
namespace NetDaemonConfig.Apps.Spotify.Types
{
public record SongItem
{
public Album? Album { get; set; }
public List<Artist>? Artists { get; set; }
public List<string>? AvailableMarkets { get; set; }
public int? DiscNumber { get; set; }
public int? DurationMs { get; set; }
public bool? Explicit { get; set; }
public ExternalIds? ExternalIds { get; set; }
public ExternalUrls? ExternalUrls { get; set; }
public string? Href { get; set; }
public string? Id { get; set; }
public string? ImageUrl { get; set; }
public bool? IsLocal { get; set; }
public object? IsPlayable { get; set; }
public string? Name { get; set; }
public int? Popularity { get; set; }
public object? PreviewUrl { get; set; }
public Restrictions? Restrictions { get; set; }
public int? TrackNumber { get; set; }
public string? Type { get; set; }
public string? Uri { get; set; }
}
public record SongResult
{
public string? Href { get; set; }
public int? Limit { get; set; }
public string? Next { get; set; }
public int? Offset { get; set; }
public object? Previous { get; set; }
public int? Total { get; set; }
public List<SongItem>? Items { get; set; }
}
public record SpotifyplusSearchTracksResponse
{
public UserProfile? UserProfile { get; set; }
public SongResult? Result { get; set; }
}
}

View file

@ -1,22 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Warning"
},
"ConsoleThemeType": "Ansi"
},
"HomeAssistant": {
"Host": "homie.nelim.org",
"Port": 443,
"Ssl": true
},
"NetDaemon": {
"ApplicationConfigurationFolder": "./apps"
},
"CodeGeneration": {
"Namespace": "HomeAssistantGenerated",
"OutputFile": "HomeAssistantGenerated.cs",
"UseAttributeBaseClasses" : "false"
}
}

View file

@ -1,103 +0,0 @@
{
config,
self,
pkgs,
...
}: let
inherit (builtins) attrValues replaceStrings;
inherit (config.sops) secrets;
inherit (pkgs.callPackage ./package.nix {}) netdaemonConfig;
in {
khepri.compositions."netdaemon" = {
networks.netdaemon = {external = true;};
services."netdaemon5" = {
image = import ./images/netdaemon.nix pkgs;
restart = "always";
environmentFiles = [secrets.netdaemon.path];
environment = {
HomeAssistant__Host = "homie.nelim.org";
HomeAssistant__Port = "443";
HomeAssistant__Ssl = "true";
NetDaemon__ApplicationAssembly = "netdaemon.dll";
Logging__LogLevel__Default = "Information"; # use Information/Debug/Trace/Warning/Error
TZ = "America/New_York";
};
volumes = ["${netdaemonConfig}:/data"];
networks = ["netdaemon"];
};
};
services.home-assistant = {
customComponents = attrValues {
inherit
(self.scopedPackages.${pkgs.system}.hass-components)
netdaemon
;
};
};
environment.systemPackages = let
nixFetchDeps =
replaceStrings [(toString self)] ["$FLAKE"]
#nix
''
let
config = (builtins.getFlake ("$FLAKE")).nixosConfigurations.homie;
inherit (config) pkgs;
netdaemonConfig = pkgs.callPackage ${toString ./package.nix} {};
in
netdaemonConfig.fetch-deps
'';
in [
(pkgs.writeShellApplication {
name = "bumpNetdaemonDeps";
runtimeInputs = with pkgs; [
dos2unix
dotnet-sdk_9
];
text = ''
# Install codegen
dotnet tool install --create-manifest-if-needed NetDaemon.HassModel.CodeGen --version "$(cat ./.version)"
# Run it
dotnet tool run nd-codegen -token "$(sed 's/HomeAssistant__Token=//' ${secrets.netdaemon.path})"
dos2unix ./HomeAssistantGenerated.cs
# This is to not have it count towards CSharp in the repo
mv ./HomeAssistantGenerated.cs ./HomeAssistantGenerated
# Update all nugets to latest versions
regex='PackageReference Include="([^"]*)" Version="([^"]*)"'
find . -type f -name '*.csproj' | while read -r file; do
# Extract unique package names from the .csproj file
packages=$(grep -oP "$regex" "$file" | sed -E 's/.*Include="([^"]*)".*/\1/' | sort -u)
# Loop through each package and update
for package in $packages; do
echo -e "\033[35mUpdate $file package: $package\033[0m"
dotnet add "$file" package "$package"
done
done
$(nix build --no-link --print-out-paths --impure --expr "$(cat <<EOF
${nixFetchDeps}
EOF
)") .
alejandra -q .
rm -r "$FLAKE/.config"
sed -i "s/finalImageTag = .*/finalImageTag = \"$(cat ./.version)\";/" ./images/netdaemon.nix
updateImages .
'';
})
];
}

View file

@ -1,297 +0,0 @@
[
{
"pname": "Cronos",
"version": "0.9.0",
"hash": "sha256-yDYBfqSXqvT/VPUf6UT3XOgqqPmOMYqhjCBxpF5i15c="
},
{
"pname": "FuzzySharp",
"version": "2.0.2",
"hash": "sha256-GuWqVOo+AG8MSvIbusLPjKfJFQRJhSSJ9eGWljTBA/c="
},
{
"pname": "Microsoft.CodeAnalysis.Analyzers",
"version": "3.3.4",
"hash": "sha256-qDzTfZBSCvAUu9gzq2k+LOvh6/eRvJ9++VCNck/ZpnE="
},
{
"pname": "Microsoft.CodeAnalysis.Common",
"version": "4.12.0",
"hash": "sha256-mm/OKG3zPLAeTVGZtuLxSG+jpQDOchn1oyHqBBJW2Ho="
},
{
"pname": "Microsoft.CodeAnalysis.CSharp",
"version": "4.12.0",
"hash": "sha256-m1i1Q5pyEq4lAoYjNE9baEjTplH8+bXx5wSA+eMmehk="
},
{
"pname": "Microsoft.Extensions.Configuration",
"version": "9.0.0",
"hash": "sha256-uBLeb4z60y8z7NelHs9uT3cLD6wODkdwyfJm6/YZLDM="
},
{
"pname": "Microsoft.Extensions.Configuration.Abstractions",
"version": "9.0.0",
"hash": "sha256-xtG2USC9Qm0f2Nn6jkcklpyEDT3hcEZOxOwTc0ep7uc="
},
{
"pname": "Microsoft.Extensions.Configuration.Binder",
"version": "9.0.0",
"hash": "sha256-6ajYWcNOQX2WqftgnoUmVtyvC1kkPOtTCif4AiKEffU="
},
{
"pname": "Microsoft.Extensions.Configuration.CommandLine",
"version": "9.0.0",
"hash": "sha256-RE6DotU1FM1sy5p3hukT+WOFsDYJRsKX6jx5vhlPceM="
},
{
"pname": "Microsoft.Extensions.Configuration.EnvironmentVariables",
"version": "9.0.0",
"hash": "sha256-tDJx2prYZpr0RKSwmJfsK9FlUGwaDmyuSz2kqQxsWoI="
},
{
"pname": "Microsoft.Extensions.Configuration.FileExtensions",
"version": "9.0.0",
"hash": "sha256-PsLo6mrLGYfbi96rfCG8YS1APXkUXBG4hLstpT60I4s="
},
{
"pname": "Microsoft.Extensions.Configuration.Json",
"version": "9.0.0",
"hash": "sha256-qQn7Ol0CvPYuyecYWYBkPpTMdocO7I6n+jXQI2udzLI="
},
{
"pname": "Microsoft.Extensions.Configuration.UserSecrets",
"version": "9.0.0",
"hash": "sha256-GoEk+Qq7lbiwWurHYx1LkDaUzIpOzaoTiVGDPfViGak="
},
{
"pname": "Microsoft.Extensions.DependencyInjection",
"version": "9.0.0",
"hash": "sha256-dAH52PPlTLn7X+1aI/7npdrDzMEFPMXRv4isV1a+14k="
},
{
"pname": "Microsoft.Extensions.DependencyInjection.Abstractions",
"version": "9.0.0",
"hash": "sha256-CncVwkKZ5CsIG2O0+OM9qXuYXh3p6UGyueTHSLDVL+c="
},
{
"pname": "Microsoft.Extensions.DependencyModel",
"version": "9.0.0",
"hash": "sha256-xirwlMWM0hBqgTneQOGkZ8l45mHT08XuSSRIbprgq94="
},
{
"pname": "Microsoft.Extensions.Diagnostics",
"version": "9.0.0",
"hash": "sha256-JMbhtjdcWRlrcrbgPlowfj26+pM+MYhnPIaYKnv9byU="
},
{
"pname": "Microsoft.Extensions.Diagnostics.Abstractions",
"version": "9.0.0",
"hash": "sha256-wG1LcET+MPRjUdz3HIOTHVEnbG/INFJUqzPErCM79eY="
},
{
"pname": "Microsoft.Extensions.FileProviders.Abstractions",
"version": "9.0.0",
"hash": "sha256-mVfLjZ8VrnOQR/uQjv74P2uEG+rgW72jfiGdSZhIfDc="
},
{
"pname": "Microsoft.Extensions.FileProviders.Physical",
"version": "9.0.0",
"hash": "sha256-IzFpjKHmF1L3eVbFLUZa2N5aH3oJkJ7KE1duGIS7DP8="
},
{
"pname": "Microsoft.Extensions.FileSystemGlobbing",
"version": "9.0.0",
"hash": "sha256-eBLa8pW/y/hRj+JbEr340zbHRABIeFlcdqE0jf5/Uhc="
},
{
"pname": "Microsoft.Extensions.Hosting",
"version": "9.0.0",
"hash": "sha256-apIN4Cz86ujsMp/ibxcvguA9uCFaFqOsZ4kAUPX5ASI="
},
{
"pname": "Microsoft.Extensions.Hosting.Abstractions",
"version": "9.0.0",
"hash": "sha256-NhEDqZGnwCDFyK/NKn1dwLQExYE82j1YVFcrhXVczqY="
},
{
"pname": "Microsoft.Extensions.Http",
"version": "9.0.0",
"hash": "sha256-MsStH3oUfyBbcSEoxm+rfxFBKI/rtB5PZrSGvtDjVe0="
},
{
"pname": "Microsoft.Extensions.Logging",
"version": "9.0.0",
"hash": "sha256-kR16c+N8nQrWeYLajqnXPg7RiXjZMSFLnKLEs4VfjcM="
},
{
"pname": "Microsoft.Extensions.Logging.Abstractions",
"version": "9.0.0",
"hash": "sha256-iBTs9twjWXFeERt4CErkIIcoJZU1jrd1RWCI8V5j7KU="
},
{
"pname": "Microsoft.Extensions.Logging.Configuration",
"version": "9.0.0",
"hash": "sha256-ysPjBq64p6JM4EmeVndryXnhLWHYYszzlVpPxRWkUkw="
},
{
"pname": "Microsoft.Extensions.Logging.Console",
"version": "9.0.0",
"hash": "sha256-N2t9EUdlS6ippD4Z04qUUyBuQ4tKSR/8TpmKScb5zRw="
},
{
"pname": "Microsoft.Extensions.Logging.Debug",
"version": "9.0.0",
"hash": "sha256-5W6fP9Eb98U3MTWKeLzSNl2cRFpE694OOPjpWp/qTAk="
},
{
"pname": "Microsoft.Extensions.Logging.EventLog",
"version": "9.0.0",
"hash": "sha256-mIL1I85Ef5+/mXl24odoUpcXet+jCZTRtKCd5z6YUwI="
},
{
"pname": "Microsoft.Extensions.Logging.EventSource",
"version": "9.0.0",
"hash": "sha256-pplZskMsR3gGbs3I0wycGsvIMPIpfWFJpOsR9GkiYRw="
},
{
"pname": "Microsoft.Extensions.Options",
"version": "9.0.0",
"hash": "sha256-DT5euAQY/ItB5LPI8WIp6Dnd0lSvBRP35vFkOXC68ck="
},
{
"pname": "Microsoft.Extensions.Options.ConfigurationExtensions",
"version": "9.0.0",
"hash": "sha256-r1Z3sEVSIjeH2UKj+KMj86har68g/zybSqoSjESBcoA="
},
{
"pname": "Microsoft.Extensions.Primitives",
"version": "9.0.0",
"hash": "sha256-ZNLusK1CRuq5BZYZMDqaz04PIKScE2Z7sS2tehU7EJs="
},
{
"pname": "NetDaemon.AppModel",
"version": "24.51.0",
"hash": "sha256-gZGv0mo8Y3c/ppfRDfQq0aLTepgmnOMOhVMTnl7qOyQ="
},
{
"pname": "NetDaemon.AppModel.SourceDeployedApps",
"version": "24.51.0",
"hash": "sha256-NHsDb7ChPYC6+QBACvi2mFsa6VAJqEh1qegA8RHY3TU="
},
{
"pname": "NetDaemon.Client",
"version": "24.51.0",
"hash": "sha256-E4B61fI9DuczvxvkYEr9QJFaKNYg2vfVk3awXSBMaW4="
},
{
"pname": "NetDaemon.Extensions.Logging",
"version": "24.51.0",
"hash": "sha256-Jvk0shJ1ld+twM6KqlSdZZjGfaNfCFMgD6SHEMIzXAw="
},
{
"pname": "NetDaemon.Extensions.Scheduling",
"version": "24.51.0",
"hash": "sha256-TlrsICPnrUqfpVxFtmXxdZPeB3u6LGJDLb8ZULcYIYA="
},
{
"pname": "NetDaemon.Extensions.Tts",
"version": "24.51.0",
"hash": "sha256-0Qh5Rs7oGCu3IWa8c4EV9j5RgLE+MJAA1PVDUqs3Zvg="
},
{
"pname": "NetDaemon.HassModel",
"version": "24.51.0",
"hash": "sha256-d9SP2Neh8any4xxdpngpCy/OBGMHdjmIRCiDaCkjXpM="
},
{
"pname": "NetDaemon.HassModel.Integration",
"version": "24.51.0",
"hash": "sha256-SelTI+6nD9+MEtWG1IdL+g8DFiIBwv0TuXiLORsVmZI="
},
{
"pname": "NetDaemon.Runtime",
"version": "24.51.0",
"hash": "sha256-VCEBzcJmMbabUnnddyAP/lua/q412WQP38ZoTdXDaXg="
},
{
"pname": "Serilog",
"version": "4.0.0",
"hash": "sha256-j8hQ5TdL1TjfdGiBO9PyHJFMMPvATHWN1dtrrUZZlNw="
},
{
"pname": "Serilog",
"version": "4.2.0",
"hash": "sha256-7f3EpCsEbDxXgsuhE430KVI14p7oDUuCtwRpOCqtnbs="
},
{
"pname": "Serilog.AspNetCore",
"version": "9.0.0",
"hash": "sha256-h58CFtXBRvwhTCrhQPHQMKbp98YiK02o+cOyOmktVpQ="
},
{
"pname": "Serilog.Extensions.Hosting",
"version": "9.0.0",
"hash": "sha256-bidr2foe7Dp4BJOlkc7ko0q6vt9ITG3IZ8b2BKRa0pw="
},
{
"pname": "Serilog.Extensions.Logging",
"version": "9.0.0",
"hash": "sha256-aGkz1V4HVl0rWC1BkcnLhG1EC7WLBoT3tdLdUUTFXaw="
},
{
"pname": "Serilog.Formatting.Compact",
"version": "3.0.0",
"hash": "sha256-nejEYqJEMG9P2iFZvbsCUPr5LZRtxbdUTLCI9N71jHY="
},
{
"pname": "Serilog.Settings.Configuration",
"version": "9.0.0",
"hash": "sha256-Q/q5UiSrcxoy5a/orod20E2RfiRtHDhxjjGMe1dW35I="
},
{
"pname": "Serilog.Sinks.Console",
"version": "6.0.0",
"hash": "sha256-QH8ykDkLssJ99Fgl+ZBFBr+RQRl0wRTkeccQuuGLyro="
},
{
"pname": "Serilog.Sinks.Debug",
"version": "3.0.0",
"hash": "sha256-7/LmoRF1rUDFhJ47bTRQQFRgSHnZDO8484r3sCGqYvE="
},
{
"pname": "Serilog.Sinks.File",
"version": "6.0.0",
"hash": "sha256-KQmlUpG9ovRpNqKhKe6rz3XMLUjkBqjyQhEm2hV5Sow="
},
{
"pname": "System.Collections.Immutable",
"version": "8.0.0",
"hash": "sha256-F7OVjKNwpqbUh8lTidbqJWYi476nsq9n+6k0+QVRo3w="
},
{
"pname": "System.Diagnostics.EventLog",
"version": "9.0.0",
"hash": "sha256-tPvt6yoAp56sK/fe+/ei8M65eavY2UUhRnbrREj/Ems="
},
{
"pname": "System.IO.Pipelines",
"version": "9.0.0",
"hash": "sha256-vb0NrPjfEao3kfZ0tavp2J/29XnsQTJgXv3/qaAwwz0="
},
{
"pname": "System.Reactive",
"version": "6.0.1",
"hash": "sha256-Lo5UMqp8DsbVSUxa2UpClR1GoYzqQQcSxkfyFqB/d4Q="
},
{
"pname": "System.Reflection.Metadata",
"version": "8.0.0",
"hash": "sha256-dQGC30JauIDWNWXMrSNOJncVa1umR1sijazYwUDdSIE="
},
{
"pname": "YamlDotNet",
"version": "16.2.1",
"hash": "sha256-Nu/rD43sihE4PTHC5r2Ua2gafclqcd2U95RcNFvGFhc="
}
]

View file

@ -1,8 +0,0 @@
pkgs:
pkgs.dockerTools.pullImage rec {
imageName = "netdaemon/netdaemon5";
imageDigest = "sha256:983727221f52dba8375b5e90fe5e4e660e88fa5f2fe67cdd1141c3c7643b5ba7";
hash = "sha256-kf7fP7x0PUaMivcLNOZ+3oH3d+NrpyCy74N7nlshywU=";
finalImageName = imageName;
finalImageTag = "24.51.0";
}

View file

@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>13.0</LangVersion>
<Nullable>enable</Nullable>
<RootNamespace>NetDaemonConfig</RootNamespace>
<Version>$([System.IO.File]::ReadAllText(".version"))</Version>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<None Update="$(MSBuildProjectDir)appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
<None Update="$(MSBuildProjectDir)appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="AfterPublishMessage" AfterTargets="Publish">
<Message Text="Publish done!" Importance="high" />
</Target>
<ItemGroup>
<PackageReference Include="NetDaemon.AppModel" Version="24.51.0" />
<PackageReference Include="NetDaemon.AppModel.SourceDeployedApps" Version="24.51.0" />
<PackageReference Include="NetDaemon.Runtime" Version="24.51.0" />
<PackageReference Include="NetDaemon.HassModel" Version="24.51.0" />
<PackageReference Include="NetDaemon.HassModel.Integration" Version="24.51.0" />
<PackageReference Include="NetDaemon.Client" Version="24.51.0" />
<PackageReference Include="NetDaemon.Extensions.Scheduling" Version="24.51.0" />
<PackageReference Include="NetDaemon.Extensions.Logging" Version="24.51.0" />
<PackageReference Include="NetDaemon.Extensions.Tts" Version="24.51.0" />
<PackageReference Include="FuzzySharp" Version="2.0.2" />
</ItemGroup>
</Project>

View file

@ -1,41 +0,0 @@
{
lib,
buildDotnetModule,
dotnetCorePackages,
}: let
inherit (lib) any hasInfix hasSuffix removeSuffix;
srcDirs = ["apps"];
srcPatterns = [".cs" ".csproj" ".json" ".version" "HomeAssistantGenerated"];
pname = "netdaemon-config";
in
buildDotnetModule {
inherit pname;
version = removeSuffix "\n" (builtins.readFile ./.version);
src = builtins.path {
name = "netdaemon-src";
path = ./.;
filter = file: type:
(type == "directory" && any (s: hasInfix s file) srcDirs)
|| any (s: hasSuffix s file) srcPatterns;
};
preBuild = ''
mv HomeAssistantGenerated HomeAssistantGenerated.cs
'';
projectFile = "netdaemon.csproj";
nugetDeps = ./deps.json;
dotnet-sdk = dotnetCorePackages.sdk_9_0;
dotnet-runtime = dotnetCorePackages.runtime_9_0;
executables = [];
postFixup = ''
cp -r $out/lib/${pname} $netdaemonConfig
'';
outputs = ["out" "netdaemonConfig"];
}

View file

@ -1,37 +0,0 @@
using System;
using System.Reactive.Linq;
using System.Reflection;
using HomeAssistantGenerated;
using Microsoft.Extensions.Hosting;
using NetDaemon.AppModel;
using NetDaemon.Extensions.Logging;
using NetDaemon.Extensions.Scheduler;
using NetDaemon.Extensions.Tts;
using NetDaemon.Runtime;
try
{
await Host.CreateDefaultBuilder(args)
.UseNetDaemonDefaultLogging()
.UseNetDaemonRuntime()
.UseNetDaemonTextToSpeech()
.ConfigureServices(static (_, services) =>
services
.AddAppsFromAssembly(Assembly.GetExecutingAssembly())
.AddNetDaemonStateManager()
.AddNetDaemonScheduler()
.AddHomeAssistantGenerated()
)
.Build()
.RunAsync()
.ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine($"Failed to start host... {e}");
throw;
}

View file

@ -1,93 +0,0 @@
{
config,
pkgs,
self,
...
}: {
services.home-assistant = {
customComponents = builtins.attrValues {
inherit
(self.scopedPackages.${pkgs.system}.hass-components)
spotifyplus
;
};
extraComponents = [
"spotify"
];
customSentences."assist_spotify" = import ./sentences.nix;
configFiles.
".storage/SpotifyWebApiPython_librespot_credentials.json"
.source = config.sops.secrets.spotifyd.path;
config.intent_script = {
PlayAlbum = {
async_action = false;
action = [
{
service = "netdaemon.spotify_play_album";
data = {
artist = "{{ artist }}";
album = "{{ album }}";
};
}
];
};
PlayArtist = {
async_action = false;
action = [
{
service = "netdaemon.spotify_play_artist";
data.artist = "{{ artist }}";
}
];
};
PlayPlaylist = {
async_action = false;
action = [
{
service = "netdaemon.spotify_play_playlist";
data.playlist = "{{ playlist }}";
}
];
};
PlaySong = {
async_action = false;
action = [
{
service = "netdaemon.spotify_play_song";
data = {
artist = "{{ artist }}";
song = "{{ song }}";
};
}
];
};
Pause = {
async_action = false;
action = [
{
service = "netdaemon.spotify_pause_unpause";
data.pause = true;
}
];
};
Unpause = {
async_action = false;
action = [
{
service = "netdaemon.spotify_pause_unpause";
data.pause = false;
}
];
};
};
};
}

Some files were not shown because too many files have changed in this diff Show more