This commit is contained in:
Bryan Ramos 2026-03-15 11:08:33 -04:00
commit 072951659a
114 changed files with 6922 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

4
.git-crypt/.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
# Do not edit this file. To specify the files to encrypt, create your own
# .gitattributes file in the directory where your files are.
* !filter !diff
*.gpg binary

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
**/*.key filter=git-crypt diff=git-crypt

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.qcow2
result
.direnv

15
.gitmodules vendored Normal file
View file

@ -0,0 +1,15 @@
[submodule "nvim"]
path = user/modules/neovim/nvim
url = https://github.com/itme-brain/nvim.git
[submodule "vim"]
path = user/modules/vim/vim
url = https://github.com/itme-brain/vim.git
[submodule "git"]
path = user/modules/git/git
url = https://github.com/itme-brain/git.git
[submodule "bash"]
path = user/modules/bash/bash
url = https://github.com/itme-brain/bash.git

19
.sops.yaml Normal file
View file

@ -0,0 +1,19 @@
# sops-nix configuration
# Per-machine age keys - add new machines here to grant access
keys:
# Machines
- &desktop age17ejyzyk52unr6eyaa9rpunxpmf7u9726v6sx7me3ww3mdu5xzgjqsgj9gl
- &server age198jg29ryg3c0qj3yg6y9ha4ce2ue4hjdaa9kalf49fxju74dhchsquvjzp
creation_rules:
# Desktop secrets
- path_regex: secrets/system/wifi\.yaml$ # Home WIFI Credentials
key_groups:
- age:
- *desktop
# Server secrets (cameras)
- path_regex: secrets/system/cameras\.yaml$ # RTSP Feed
key_groups:
- age:
- *server

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <http://unlicense.org/>

122
README.md Normal file
View file

@ -0,0 +1,122 @@
# NixOS Configuration
Modular NixOS flake configuration with home-manager integration.
## Requirements
- [Nix with Flakes](https://nixos.wiki/wiki/Flakes#Enable_flakes_permanently_in_NixOS)
- [NixOS](https://www.nixos.org/) for system configurations
- [Home-Manager](https://nix-community.github.io/home-manager/) for user configurations
## Flake Outputs
| Configuration | Description |
|---------------|-------------|
| `desktop` | Primary workstation |
| `server` | Home server |
| `wsl` | Windows Subsystem for Linux |
## Fresh Install
From the NixOS live installer:
```bash
# Enable flakes
echo "experimental-features = nix-command flakes" | sudo tee -a /etc/nix/nix.conf
# Clone repo
nix run nixpkgs#git -- clone --recurse-submodules https://github.com/itme-brain/nixos.git
cd nixos
# Enter dev shell and install
nix develop
just install desktop
```
## Getting Started
```bash
git clone --recurse-submodules git@github.com:itme-brain/nixos.git
cd nixos
nix develop
just
```
**Note:** Replace `hardware.nix` in `system/machines/<machine>` with output from `nixos-generate-config` for your hardware.
## Directory Structure
```
.
├── flake.nix
├── flake.lock
├── justfile
├── system/
│ ├── keys/ # Machine SSH keys
│ │ └── desktop/
│ └── machines/
│ ├── desktop/
│ │ ├── default.nix # Machine entry point
│ │ ├── hardware.nix # Hardware config
│ │ ├── system.nix # System settings
│ │ └── modules/
│ │ ├── disko/ # Disk partitioning
│ │ └── home-manager/ # Home-manager integration
│ ├── server/ # Server (same structure)
│ └── wsl/ # WSL (same structure)
└── user/
├── default.nix # User options (name, email, keys)
├── home.nix # Shared home-manager defaults
├── bookmarks/
├── keys/
│ ├── age/
│ ├── pgp/
│ └── ssh/
└── modules/
├── bash/bash/ # Shell (submodule)
├── git/git/ # Git (submodule)
├── neovim/nvim/ # Neovim (submodule)
├── vim/vim/ # Vim (submodule)
├── tmux/
├── dev/ # CLI dev tools
├── security/
│ ├── gpg/
│ └── yubikey/
├── utils/
│ ├── dev/ # Dev tools (claude-code, direnv, etc.)
│ ├── email/
│ ├── irc/
│ └── writing/
└── gui/
├── default.nix # Browser-focused mimeApps
├── wm/
│ ├── hyprland/
│ └── sway/
├── browsers/
├── alacritty/
├── dev/
│ ├── pcb/ # Arduino, KiCad
│ └── design/ # Penpot
├── corn/
├── fun/
└── utils/
```
## Architecture
**flake.nix** defines NixOS configurations that reference machines under `system/machines/`.
Each machine imports its hardware, system settings, and home-manager config.
**user/home.nix** provides shared defaults for all users:
- Essential packages
- Default modules
**Machine home.nix** imports user defaults and enables machine-specific modules.
## Resources
- [nixpkgs Packages](https://search.nixos.org/packages)
- [nixpkgs Options](https://search.nixos.org/options)
- [Home-Manager Options](https://home-manager-options.extranix.com)

204
flake.lock generated Normal file
View file

@ -0,0 +1,204 @@
{
"nodes": {
"disko": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1773025010,
"narHash": "sha256-khlHllTsovXgT2GZ0WxT4+RvuMjNeR5OW0UYeEHPYQo=",
"owner": "nix-community",
"repo": "disko",
"rev": "7b9f7f88ab3b339f8142dc246445abb3c370d3d3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "disko",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nur",
"nixpkgs"
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1772985280,
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"repo": "home-manager",
"type": "github"
}
},
"nixos-wsl": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1739577062,
"narHash": "sha256-u/trdPzJO8UotNq48RbG7m6Pe8761IEMCOY0QidNjY4=",
"owner": "nix-community",
"repo": "NixOS-WSL",
"rev": "0b2b8b31f69f24e9a75b4b18a32c771a48612d5e",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "2411.6.0",
"repo": "NixOS-WSL",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1773375660,
"narHash": "sha256-SEzUWw2Rf5Ki3bcM26nSKgbeoqi2uYy8IHVBqOKjX3w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3e20095fe3c6cbb1ddcef89b26969a69a1570776",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1773389992,
"narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c06b4ae3d6599a672a6210b7021d699c351eebda",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1772963539,
"narHash": "sha256-9jVDGZnvCckTGdYT53d/EfznygLskyLQXYwJLKMPsZs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9dcb002ca1690658be4a04645215baea8b95f31d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nur": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1773108757,
"narHash": "sha256-3BAoe2R6YA6Xjdsgx3urZ4Ns3LeTy0E/w5d1wPny910=",
"owner": "nix-community",
"repo": "NUR",
"rev": "9f2c583704f122828e6f9893416ca3b007464ee6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "NUR",
"type": "github"
}
},
"root": {
"inputs": {
"disko": "disko",
"home-manager": "home-manager",
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable",
"nur": "nur",
"sops-nix": "sops-nix"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1773550941,
"narHash": "sha256-wa/++bL2QeMUreNFBZEWluQfOYB0MnQIeGNMuaX9sfs=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "c469b6885f0dcd5c7c56bd935a0f08dbcd9e79e1",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

78
flake.nix Normal file
View file

@ -0,0 +1,78 @@
{
description = "My Nix Configs";
inputs =
{
self.submodules = true;
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
nur = {
url = "github:nix-community/NUR";
};
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs";
};
nixos-wsl = {
url = "github:nix-community/NixOS-WSL/2411.6.0";
inputs.nixpkgs.follows = "nixpkgs";
};
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, nixpkgs-unstable, nur, ... }@inputs:
let
mkPkgs = system: import nixpkgs {
inherit system;
config = {
allowUnfree = true;
nvidia.acceptLicense = true;
};
overlays = [
nur.overlays.default
# Make unstable packages available as pkgs.unstable.*
(final: prev: {
unstable = import nixpkgs-unstable {
inherit system;
config.allowUnfree = true;
};
})
];
};
mkSystem = { path, system ? "x86_64-linux" }:
let pkgs = mkPkgs system;
in nixpkgs.lib.nixosSystem {
inherit system pkgs;
specialArgs = { inherit inputs; };
modules = [
inputs.sops-nix.nixosModules.sops
path
];
};
in
{
nixosConfigurations = {
desktop = mkSystem { path = ./system/machines/desktop; };
server = mkSystem { path = ./system/machines/server; };
wsl = mkSystem { path = ./system/machines/wsl; };
};
devShells.x86_64-linux.default = with mkPkgs "x86_64-linux"; mkShell {
name = "devShell";
packages = [
just
age
sops
];
};
};
}

366
justfile Normal file
View file

@ -0,0 +1,366 @@
SYSTEM := "$(echo $HOSTNAME)"
VALID_SYSTEMS := "desktop server wsl"
# Print this list
default:
@just --list
# Validate system argument
[private]
_validate SYSTEM:
#!/usr/bin/env bash
case "{{SYSTEM}}" in
desktop|server|wsl) ;;
*) echo "Error: Unknown system '{{SYSTEM}}'. Use one of: {{VALID_SYSTEMS}}"; exit 1 ;;
esac
# Helper to parse submodules from .gitmodules
[private]
_subs_init := '''
declare -A SUBS
while read -r key path; do
name="${key#submodule.}"; name="${name%.path}"
SUBS[$name]="$path"
done < <(git config -f .gitmodules --get-regexp 'submodule\..*\.path')
'''
# Clean up build artifacts
[group('nix')]
clean:
#!/usr/bin/env bash
set -euo pipefail
echo "Cleaning build artifacts"
rm -f result
rm -f ./*.qcow2
echo "Done"
# Output what derivations will be built
[group('nix')]
out SYSTEM="desktop": (_validate SYSTEM)
@echo "Outputting derivations to be built for {{SYSTEM}}..."
@nix build --dry-run .#nixosConfigurations."{{SYSTEM}}".config.system.build.toplevel -L
# Test switch into the next generation
[group('nixos')]
test SYSTEM=SYSTEM: (_validate SYSTEM)
@echo "Testing switching to next NixOS generation for {{SYSTEM}}..."
@sudo nixos-rebuild test --flake .#{{SYSTEM}}
# Build the nix expression and hydrate the results directory
[group('nix')]
build SYSTEM="desktop": (_validate SYSTEM)
@echo "Building NixOS configuration for {{SYSTEM}}..."
@nix build .#nixosConfigurations."{{SYSTEM}}".config.system.build.toplevel -L
@echo -e "\033[32mBuild success - result directory hydrated\033[0m"
# Deploy a vm of the defined system
[group('nixos')]
vm SYSTEM: (_validate SYSTEM)
#!/usr/bin/env bash
set -euo pipefail
echo "Building VM for {{SYSTEM}}..."
nixos-rebuild build-vm --flake .#{{SYSTEM}}
if [[ -f result/bin/run-{{SYSTEM}}-vm ]]; then
result/bin/run-{{SYSTEM}}-vm
else
echo "Error: VM build failed!"
exit 1
fi
# grep nixpkgs for PKG
[group('nix')]
search PKG:
nix search nixpkgs {{PKG}}
# Open nixos packages in the browser
[group('nix')]
pkgs:
@xdg-open https://search.nixos.org/packages
# Open nixos options in the browser
[group('nix')]
options:
@xdg-open https://search.nixos.org/options
# NixOS-rebuild switch for the current system
[group('nixos')]
switch:
@echo -e "\033[32m->> Switching to next generation ->>\033[0m"
@sudo nixos-rebuild switch --flake .#{{SYSTEM}}
# Rollback to previous generation
[group('nixos')]
rollback:
@sudo nixos-rebuild switch --rollback
# NixOS-rebuild boot for the current system
[group('nixos')]
boot:
@echo -e "\033[34m->> Reboot to new generation ->>\033[0m"
@sudo nixos-rebuild boot --flake .#{{SYSTEM}}
# Partition disk only (interactive disk selection)
[group('nixos')]
partition SYSTEM:
#!/usr/bin/env bash
set -euo pipefail
DISKO_CONFIG="./system/machines/{{SYSTEM}}/modules/disko/default.nix"
if [[ ! -f "$DISKO_CONFIG" ]]; then
echo "Error: No disko config for '{{SYSTEM}}'"
exit 1
fi
# Build array of disk options with readable info
declare -a DISK_IDS
declare -a DISK_OPTIONS
for id in /dev/disk/by-id/*; do
name=$(basename "$id")
[[ "$name" =~ part ]] && continue
[[ ! "$name" =~ ^(ata|nvme|scsi)- ]] && continue
dev=$(readlink -f "$id")
dev_name=$(basename "$dev")
size=$(lsblk -dn -o SIZE "$dev" 2>/dev/null) || continue
model=$(lsblk -dn -o MODEL "$dev" 2>/dev/null | xargs) || model=""
DISK_IDS+=("$id")
DISK_OPTIONS+=("$dev_name $size $model")
done
if [[ ${#DISK_IDS[@]} -eq 0 ]]; then
echo "No disks found!"
exit 1
fi
echo "Select a disk:"
select opt in "${DISK_OPTIONS[@]}"; do
if [[ -n "$opt" ]]; then
idx=$((REPLY - 1))
DISK="${DISK_IDS[$idx]}"
break
else
echo "Invalid selection"
fi
done
echo ""
echo -e "\033[31m!! WARNING: This will DESTROY all data on $DISK !!\033[0m"
read -p "Continue? [y/N]: " confirm
case "${confirm,,}" in
y|yes) ;;
*) echo "Aborted."; exit 1 ;;
esac
echo "Writing disk '$DISK' to disko config..."
sed -i "s|device = \"/dev/disk/by-id/[^\"]*\";|device = \"$DISK\";|" "$DISKO_CONFIG"
echo "Partitioning $DISK..."
sudo nix \
--extra-experimental-features "nix-command flakes" \
run github:nix-community/disko -- \
--mode destroy,format,mount \
"$DISKO_CONFIG"
echo -e "\033[32mPartitioning complete. Disk mounted at /mnt.\033[0m"
# Install NixOS (partition + install in one shot)
[group('nixos')]
install SYSTEM:
#!/usr/bin/env bash
set -euo pipefail
DISKO_CONFIG="./system/machines/{{SYSTEM}}/modules/disko/default.nix"
if [[ ! -f "$DISKO_CONFIG" ]]; then
echo "Error: No disko config for '{{SYSTEM}}'"
exit 1
fi
# Build array of disk options with readable info
declare -a DISK_IDS
declare -a DISK_OPTIONS
for id in /dev/disk/by-id/*; do
name=$(basename "$id")
[[ "$name" =~ part ]] && continue
[[ ! "$name" =~ ^(ata|nvme|scsi)- ]] && continue
dev=$(readlink -f "$id")
dev_name=$(basename "$dev")
size=$(lsblk -dn -o SIZE "$dev" 2>/dev/null) || continue
model=$(lsblk -dn -o MODEL "$dev" 2>/dev/null | xargs) || model=""
DISK_IDS+=("$id")
DISK_OPTIONS+=("$dev_name $size $model")
done
if [[ ${#DISK_IDS[@]} -eq 0 ]]; then
echo "No disks found!"
exit 1
fi
echo "Select a disk:"
select opt in "${DISK_OPTIONS[@]}"; do
if [[ -n "$opt" ]]; then
idx=$((REPLY - 1))
DISK="${DISK_IDS[$idx]}"
break
else
echo "Invalid selection"
fi
done
echo ""
echo -e "\033[31m!! WARNING: This will DESTROY all data on $DISK !!\033[0m"
read -p "Continue? [y/N]: " confirm
case "${confirm,,}" in
y|yes) ;;
*) echo "Aborted."; exit 1 ;;
esac
echo "Writing disk '$DISK' to disko config..."
sed -i "s|device = \"/dev/disk/by-id/[^\"]*\";|device = \"$DISK\";|" "$DISKO_CONFIG"
echo "Partitioning and installing NixOS..."
sudo nix \
--extra-experimental-features "nix-command flakes" \
run github:nix-community/disko/latest#disko-install -- \
--flake .#{{SYSTEM}} \
--disk main "$DISK"
echo -e "\033[32mDone! Reboot to start NixOS.\033[0m"
# Commit all changes and push to upstream
[group('git')]
gh COMMIT_MESSAGE:
#!/usr/bin/env bash
set -euo pipefail
git add -A
git commit -m "{{COMMIT_MESSAGE}}"
git push
# Show status of submodules with changes
[group('submodule')]
sstatus:
#!/usr/bin/env bash
{{_subs_init}}
for name in "${!SUBS[@]}"; do
status=$(git -C "${SUBS[$name]}" status -s)
[[ -n "$status" ]] && echo -e "\033[34m$name:\033[0m" && echo "$status"
done
# Pull all submodules and parent
[group('submodule')]
spull:
#!/usr/bin/env bash
set -euo pipefail
{{_subs_init}}
git pull
for name in "${!SUBS[@]}"; do
echo -e "\033[34m$name:\033[0m"
git -C "${SUBS[$name]}" pull
done
# Push submodules and parent
[group('submodule')]
spush NAME="":
#!/usr/bin/env bash
set -euo pipefail
{{_subs_init}}
if [[ -n "{{NAME}}" ]]; then
path="${SUBS[{{NAME}}]:-}"
[[ -z "$path" ]] && echo "Unknown: {{NAME}}. Available: ${!SUBS[*]}" && exit 1
git -C "$path" push
else
for path in "${SUBS[@]}"; do git -C "$path" push; done
fi
git push
# Commit submodule changes and update parent
[group('submodule')]
scommit NAME="":
#!/usr/bin/env bash
set -euo pipefail
{{_subs_init}}
MSGS=()
commit_sub() {
local name="$1" path="$2"
[[ -z "$(git -C "$path" status -s)" ]] && return 0
echo -e "\033[34m$name:\033[0m"
git -C "$path" status -s
read -p "Commit message: " MSG
[[ -z "$MSG" ]] && return 0
git -C "$path" add -A && git -C "$path" commit -m "$MSG"
git add "$path"
MSGS+=("$name: $MSG")
}
if [[ -n "{{NAME}}" ]]; then
path="${SUBS[{{NAME}}]:-}"
[[ -z "$path" ]] && echo "Unknown: {{NAME}}. Available: ${!SUBS[*]}" && exit 1
commit_sub "{{NAME}}" "$path"
else
for name in "${!SUBS[@]}"; do commit_sub "$name" "${SUBS[$name]}"; done
fi
if ! git diff --cached --quiet; then
COMMIT_MSG="updated submodules"$'\n'
for m in "${MSGS[@]}"; do COMMIT_MSG+="- $m"$'\n'; done
git commit -m "$COMMIT_MSG"
fi
# Commit and push submodules + parent
[group('submodule')]
ssync NAME="":
#!/usr/bin/env bash
set -euo pipefail
{{_subs_init}}
MSGS=()
sync_sub() {
local name="$1" path="$2"
[[ -z "$(git -C "$path" status -s)" ]] && return 0
echo -e "\033[34m$name:\033[0m"
git -C "$path" status -s
read -p "Commit message: " MSG
[[ -z "$MSG" ]] && return 0
git -C "$path" add -A && git -C "$path" commit -m "$MSG"
git -C "$path" push
git add "$path"
MSGS+=("$name: $MSG")
}
if [[ -n "{{NAME}}" ]]; then
path="${SUBS[{{NAME}}]:-}"
[[ -z "$path" ]] && echo "Unknown: {{NAME}}. Available: ${!SUBS[*]}" && exit 1
sync_sub "{{NAME}}" "$path"
else
for name in "${!SUBS[@]}"; do sync_sub "$name" "${SUBS[$name]}"; done
fi
if ! git diff --cached --quiet; then
COMMIT_MSG="updated submodules"$'\n'
for m in "${MSGS[@]}"; do COMMIT_MSG+="- $m"$'\n'; done
git commit -m "$COMMIT_MSG"
fi
git push
# Fetch resources and compute sha256 hash
[group('nix')]
hash URL:
#!/usr/bin/env bash
set -euo pipefail
if [[ "{{URL}}" =~ \.(tar(\.gz)?|tgz|gz|zip)$ ]]; then
CONTENTS=$(nix-prefetch-url --unpack {{URL}})
else
CONTENTS=$(nix-prefetch-url {{URL}})
fi
HASH=$(nix hash convert --hash-algo sha256 "$CONTENTS")
echo -e "\033[32m$HASH\033[0m"

140
secrets/README.md Normal file
View file

@ -0,0 +1,140 @@
# Secrets Management
```
secrets/
├── system/ # System-level secrets (WiFi, VPN, etc.)
└── user/ # User-level secrets (password-store, API keys, etc.)
```
## Prerequisites
Age identity files are stored in `src/user/config/keys/age/` and deployed automatically.
```bash
# For testing with a local key:
age-keygen > src/user/config/keys/age/local
# For Yubikey (see "Migrating to Yubikey" below):
age-plugin-yubikey --identity > src/user/config/keys/age/yubikey
# Add the public key to .sops.yaml in repo root
```
After rebuild, the identity is written to `~/.config/sops/age/keys.txt`.
## Adding Secrets
1. Create or edit a YAML file:
```bash
vim secrets/system/example.yaml
```
2. Encrypt in place:
```bash
sops -e -i secrets/system/example.yaml
```
3. Reference in NixOS config:
```nix
sops.secrets."SECRET_NAME" = {
sopsFile = path/to/example.yaml;
};
```
## Editing Secrets
```bash
# Opens decrypted in $EDITOR, re-encrypts on save
sops secrets/system/wifi.yaml
```
## Viewing Secrets
```bash
# Decrypt to stdout
sops -d secrets/system/wifi.yaml
```
## Removing Secrets
1. Remove from NixOS config
2. Delete the encrypted file or remove the key from it via `sops`
## Re-keying (after adding/removing age keys)
```bash
# Update .sops.yaml with new keys, then:
sops updatekeys secrets/system/wifi.yaml
```
## Migrating to Yubikey
### 1. Generate a new age identity on Yubikey
```bash
# Insert Yubikey and run interactive setup
age-plugin-yubikey
# Follow prompts:
# - Select slot (default: 1)
# - Set PIN policy (default: once per session)
# - Set touch policy (recommended: always)
#
# This generates a NEW key on the Yubikey - you will not know the private key.
# Save the identity to the keys directory:
age-plugin-yubikey --identity > src/user/config/keys/age/yubikey
```
The identity file only contains a *reference* to the Yubikey, not the private key.
It will be deployed to `~/.config/sops/age/keys.txt` on rebuild.
### 2. Update .sops.yaml with Yubikey public key
```bash
# Get the public key (age1yubikey1...)
age-plugin-yubikey --list
# Edit .sops.yaml and replace/add the key:
vim .sops.yaml
```
```yaml
keys:
- &yubikey age1yubikey1q... # your Yubikey public key
creation_rules:
- path_regex: secrets/.*\.yaml$
key_groups:
- age:
- *yubikey
```
### 3. Re-key all secrets against the new key
```bash
# This decrypts with your OLD key and re-encrypts with the NEW key
find secrets -name "*.yaml" -exec sops updatekeys {} \;
```
You'll need your old key available during this step.
### 4. Remove the old age key (optional)
```bash
# Once all secrets are re-keyed and tested:
# 1. Remove old key from .sops.yaml
# 2. Delete the old key file from the repo:
rm src/user/config/keys/age/local # or whatever your test key was named
```
### 5. Test decryption with Yubikey
```bash
# Should prompt for Yubikey touch/PIN
sops -d secrets/system/wifi.yaml
# Test a full rebuild
sudo nixos-rebuild switch --flake .#desktop
```
If decryption works, your migration is complete.

View file

@ -0,0 +1,17 @@
RTSP_USER: ENC[AES256_GCM,data:yketGXU=,iv:KQVYzBjzkkDepiD+hjGWLjvyC3iySK6JMZ9Fyrdo1Eo=,tag:7sHqOYROk6qNd56xWex1Bw==,type:str]
RTSP_PASS: ENC[AES256_GCM,data:QGfg7bZVdGAjuw==,iv:uS/6XpHlMgpZ812tVxGFjwMeyqX5YvfBNJUVuc0C+z8=,tag:5SIdu/yGVxzhYclyOUrOCg==,type:str]
sops:
age:
- recipient: age198jg29ryg3c0qj3yg6y9ha4ce2ue4hjdaa9kalf49fxju74dhchsquvjzp
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwSys1ZzdwRmRybkR2TGFn
RG1wVGI4aTNkYTZpOUtUSlBJQTVnU1JsdmpzCklLdUY0K1ZjSzhId3NVNXcvUWl0
eE95cmVHWGNsZVNYWHQvSXlNZjl5WWMKLS0tIFBpek81aGlhUXUxWm91ZjV1RFk0
SzZFalY2NXJOMFNSVFVxbDZPb1Q1amsKaDZqJvFfqxhqVcd5ldRHC+3XC/lBb9N7
VUQ/hQZM5a1WUk321Y2bBXTN6cE/06UYrl6HXwZgxTVydou4eHywww==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-15T06:01:25Z"
mac: ENC[AES256_GCM,data:EwPCYlEKUgjcrZ0c75UH7n9FjkbF+WEMQzJ7Xb1+fXkD0zIIVgjudgCNtwwJTbSVupyuCVcJfCKN9n4kBpG+HyIqDZQl1MTy5YzcvvMoj3rkPLIRMfkLXFs4FRe/cFKFdxARbQrlEJqfgQME8/M07Bl+VcZRIq0mz7HlrxZFbgg=,iv:WDGCSNFT8l+MEOQCWSDDtYTj9gdDoCk+kl8UdQg+9mw=,tag:4b9vRle/waBqQX284cIiNA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.1

19
secrets/system/wifi.yaml Normal file
View file

@ -0,0 +1,19 @@
WIFI_HOME_SSID: ENC[AES256_GCM,data:xZl6DE4=,iv:koEKZTW3O+bctlwoSzZCBLRT4iG380RmP/olukUd8Xc=,tag:4HM6d+FslbM1hRYcn3JTqA==,type:str]
WIFI_HOME_PSK: ENC[AES256_GCM,data:jyC4VXzhpIE=,iv:fN33x0y4kmRrPQe7ydWGdeTQaR5a3ekBaUKHX9FpHk0=,tag:tQUUj5LU6kidYTTI2RWf8w==,type:str]
WIFI_CAMS_SSID: ENC[AES256_GCM,data:yJ/oUCfSbaw=,iv:foswCMqFLOUyPQP9KL08Mhix0j2+Jt4sHHaPV49RFe4=,tag:rBG9IyQDmbNsUp4E+tnmZg==,type:str]
WIFI_CAMS_PSK: ENC[AES256_GCM,data:VlVxqxbHof6rmqSRJrXEQsT15BNl1lrghg==,iv:B6si07a0Z5ZJfMkK0HN9fa5zvQDzf7lvIQt1ZBpBZdk=,tag:21622mki8lITWA5fh7bKrg==,type:str]
sops:
age:
- recipient: age17ejyzyk52unr6eyaa9rpunxpmf7u9726v6sx7me3ww3mdu5xzgjqsgj9gl
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuaFRWRnNGVm9TTkhJVFAv
RWQ1Q2Q3RStBa0E4V2hFYUV2ZHFPZnJGdkFJClY0WThYbWk2Nmx6V0g4UU9WSGRZ
bFpNalZJRlZyWjFTMU1JK1dpWndPS3cKLS0tIHI0M3ZUVlI3TTV6c2h1WmdrdW1l
VWtxaFNVUUFHT20xVTZpSjVWRHozTzQKAAsNbFf6bU6eelqOX7Ei+Zrtw0aw0WgQ
5zOWrxd92MaG/AvVpL0jC1LuWtZeK3MK7Qpgtm8t0rgugUas16KYpA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-03-15T06:25:12Z"
mac: ENC[AES256_GCM,data:+Lhmcr2Jg1htfcMMMPu8AxrDhvlm4yLVIunxAcs4adX8NeJccD+/UVvZO+qtzF6iQmXCdTvRDo3shqmJKHvs6ZUJVe3jokTKMJoQdIbSIS0fSwULUV8evK5Incf8qzpnHd2J1Kg4qCL8oWeN9t4TBJTPVrNJzd/sOF1Kp2g9IBE=,iv:/ORst/Lnj3h16fJQWxAaJ5vMWKMN2lGhGoIQjNxNpGQ=,tag:NURI5mwbfECaWTgbSs6clA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.1

43
system/keys/default.nix Normal file
View file

@ -0,0 +1,43 @@
{ lib, ... }:
with lib;
with builtins;
let
extractName = filename:
let
noKey = removeSuffix ".key" filename;
noMarkers = replaceStrings
[ ".pub" ".priv" ".public" ".private" ]
[ "" "" "" "" ]
noKey;
in noMarkers;
constructKeys = dir: (
listToAttrs (
map (subdir: {
name = subdir;
value = listToAttrs (
map (file: {
name = extractName file;
value = readFile "${dir}/${subdir}/${file}";
}) (filter (file:
(readDir "${dir}/${subdir}").${file} == "regular" &&
hasSuffix ".key" file
) (attrNames (readDir "${dir}/${subdir}")))
);
}) (filter (node: (readDir dir).${node} == "directory") (attrNames (readDir dir)))
)
);
in
{
options = {
machines = mkOption {
description = "Machine Configurations";
type = types.attrs;
default = {
keys = constructKeys ./.;
};
};
};
}

Binary file not shown.

View file

@ -0,0 +1,19 @@
## Hardware
| Component | Model |
|-------------|------------------------------------|
| Motherboard | MSI B760 GAMING PLUS WIFI |
| CPU | Intel Core i7-12700KF (12th Gen) |
| GPU | NVIDIA GeForce GTX 1650 |
| Storage | 2x 2TB Crucial MX500 SSD |
## Memory
| Slot | Size | Manufacturer | Part Number | Speed |
|---------|------|----------------|-------------|------------|
| DIMM A1 | - | - | - | - |
| DIMM A2 | 16GB | Team Group Inc | UD5-6000 | 4800 MT/s |
| DIMM B1 | - | - | - | - |
| DIMM B2 | 16GB | Team Group Inc | UD5-6000 | 4800 MT/s |
**Total: 32GB DDR5**

View file

@ -0,0 +1,16 @@
{ inputs, ... }:
{
imports = [
inputs.disko.nixosModules.disko
(import ./modules/disko)
inputs.home-manager.nixosModules.home-manager
{ home-manager.sharedModules = [ inputs.sops-nix.homeManagerModules.sops ]; }
(import ./modules/home-manager)
../../../user
../../keys
../../modules/sops
./hardware.nix
./system.nix
];
}

View file

@ -0,0 +1,88 @@
{ config, lib, pkgs, modulesPath, ... }:
with lib;
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
options.monitors = mkOption {
type = types.listOf (types.submodule {
options = {
name = mkOption { type = types.str; example = "HDMI-A-1"; };
width = mkOption { type = types.int; };
height = mkOption { type = types.int; };
x = mkOption { type = types.int; };
y = mkOption { type = types.int; };
scale = mkOption { type = types.float; };
refreshRate = mkOption { type = types.int; };
};
});
default = [];
description = "System monitor configuration";
};
config = {
monitors = [
{ name = "HDMI-A-1"; width = 1920; height = 1080; x = 0; y = 0; scale = 1.0; refreshRate = 60; }
{ name = "DP-1"; width = 1920; height = 1080; x = 1920; y = 0; scale = 1.0; refreshRate = 60; }
];
boot = {
initrd = {
availableKernelModules = [ "vmd" "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" ];
kernelModules = [ "dm-snapshot" ];
};
extraModulePackages = [ ];
kernelPackages = pkgs.linuxPackages_zen;
kernelParams = [ "intel_iommu=on" ];
kernelModules = [ "kvm-intel" "virtio" "vfio-pci" "coretemp" ];
};
environment.systemPackages = with pkgs; [
linuxHeaders
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-extension-layer
mesa
mesa-demos
cudaPackages.cudatoolkit
cudaPackages.cudnn
nvidia-vaapi-driver
];
hardware = {
cpu = {
intel = {
updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
};
};
nvidia = {
open = true;
modesetting.enable = true;
nvidiaSettings = true;
package = config.boot.kernelPackages.nvidiaPackages.stable;
};
graphics = {
enable = true;
enable32Bit = true;
};
};
# Despite confusing name, this configures userspace nvidia libraries
services.xserver.videoDrivers = [ "nvidia" ];
virtualisation.libvirtd = {
enable = true;
qemu = {
runAsRoot = true;
};
};
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "performance";
};
}

View file

@ -0,0 +1,57 @@
{
disko.devices = {
disk = {
main = {
type = "disk";
device = "/dev/disk/by-id/ata-CT2000MX500SSD1_2137E5D2D47D";
content = {
type = "gpt";
partitions = {
boot = {
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
primary = {
size = "100%";
content = {
type = "lvm_pv";
vg = "nix";
};
};
};
};
};
};
lvm_vg = {
nix = {
type = "lvm_vg";
lvs = {
root = {
size = "5%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
mountOptions = [ "defaults" ];
};
};
home = {
size = "100%FREE";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/home";
};
};
};
};
};
};
}

View file

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

View file

@ -0,0 +1,73 @@
{ config, pkgs, ... }:
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = {
monitors = config.monitors;
};
home-manager.users.${config.user.name} = {
imports = [
../../../../../user
../../../../../user/home.nix
../../../../../user/modules
];
home.stateVersion = "23.11";
home.packages = [ pkgs.sshfs ];
systemd.user.services.nvr-mount = {
Unit = {
Description = "Mount Frigate recordings via SSHFS";
After = [ "network-online.target" ];
};
Service = {
Type = "oneshot";
RemainAfterExit = true;
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p %h/Media/nvr";
ExecStart = "${pkgs.sshfs}/bin/sshfs -o reconnect,ServerAliveInterval=15 server:/var/lib/frigate/recordings %h/Media/nvr";
ExecStop = "${pkgs.fuse}/bin/fusermount -u %h/Media/nvr";
};
Install = {
WantedBy = [ "default.target" ];
};
};
programs.ssh = {
enable = true;
enableDefaultConfig = false;
matchBlocks = {
"*" = {
serverAliveInterval = 60;
serverAliveCountMax = 3;
};
"server" = {
hostname = "192.168.0.154";
user = "bryan";
};
};
};
# Machine-specific modules
modules.user = {
vim.enable = false;
security.yubikey.enable = true;
utils = {
dev.enable = true;
irc.enable = true;
writing.enable = true;
};
gui = {
wm.hyprland.enable = true;
browser.firefox.enable = true;
alacritty.enable = true;
corn.enable = true;
fun.enable = true;
utils.enable = true;
};
};
};
}

View file

@ -0,0 +1,206 @@
{ pkgs, lib, config, ... }:
let
gpgEnabled = lib.any
(user: user.modules.user.security.gpg.enable or false)
(lib.attrValues config.home-manager.users);
in
{ system.stateVersion = "23.11";
modules.system.sops.enable = true;
# WiFi secrets
sops.secrets = let wifi = { sopsFile = ../../../secrets/system/wifi.yaml; }; in {
"WIFI_HOME_SSID" = wifi;
"WIFI_HOME_PSK" = wifi;
"WIFI_CAMS_SSID" = wifi;
"WIFI_CAMS_PSK" = wifi;
};
sops.templates."wifi-env".content = ''
WIFI_HOME_SSID=${config.sops.placeholder."WIFI_HOME_SSID"}
WIFI_HOME_PSK=${config.sops.placeholder."WIFI_HOME_PSK"}
WIFI_CAMS_SSID=${config.sops.placeholder."WIFI_CAMS_SSID"}
WIFI_CAMS_PSK=${config.sops.placeholder."WIFI_CAMS_PSK"}
'';
users.users = {
${config.user.name} = {
isNormalUser = true;
extraGroups = config.user.groups
++ [ "video" "audio" "kvm" "libvirtd" "dialout" ];
openssh.authorizedKeys.keys = [ "${config.user.keys.ssh.graphone}" ];
};
};
nix = {
channel.enable = false;
package = pkgs.nixVersions.stable;
extraOptions = ''
experimental-features = nix-command flakes
keep-going = true
'';
settings = {
auto-optimise-store = true;
trusted-users = [ "${config.user.name}" ];
substitute = true;
max-jobs = "auto";
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
};
boot.loader = {
systemd-boot = {
enable = true;
configurationLimit = 5;
#memtest86.enable = true;
};
efi = {
canTouchEfiVariables = true;
};
#timeout = null;
};
environment = {
systemPackages = with pkgs; [
vim
git
usbutils
];
pathsToLink = [
"/share/applications"
"/share/xdg-desktop-portal"
];
};
fonts.packages = with pkgs; [
nerd-fonts.terminess-ttf
];
security = {
sudo = {
wheelNeedsPassword = false;
execWheelOnly = true;
};
polkit.enable = true;
};
time = {
timeZone = "America/New_York";
hardwareClockInLocalTime = true;
};
i18n.defaultLocale = "en_US.UTF-8";
console = {
font = "Lat2-Terminus16";
useXkbConfig = true;
};
networking = {
hostName = "desktop";
useDHCP = lib.mkDefault true;
networkmanager = {
enable = true;
ensureProfiles = {
environmentFiles = [ config.sops.templates."wifi-env".path ];
profiles.wifi = {
connection = {
id = "$WIFI_HOME_SSID";
type = "wifi";
interface-name = "wlo1";
autoconnect = false; # Manual connection via nmcli
};
wifi = {
ssid = "$WIFI_HOME_SSID";
mode = "infrastructure";
};
wifi-security = {
key-mgmt = "wpa-psk";
psk = "$WIFI_HOME_PSK";
};
ipv4.method = "auto";
ipv6.method = "auto";
};
profiles.cams = {
connection = {
id = "$WIFI_CAMS_SSID";
type = "wifi";
interface-name = "wlo1";
autoconnect = false;
};
wifi = {
ssid = "$WIFI_CAMS_SSID";
mode = "infrastructure";
};
wifi-security = {
key-mgmt = "wpa-psk";
psk = "$WIFI_CAMS_PSK";
};
ipv4.method = "auto";
ipv6.method = "auto";
};
};
};
firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
};
};
services.dnsmasq = {
enable = true;
settings = {
# Explicit subdomains -> local server
address = [
"/git.ramos.codes/192.168.0.154"
"/ln.ramos.codes/192.168.0.154"
"/photos.ramos.codes/192.168.0.154"
"/test.ramos.codes/192.168.0.154"
"/electrum.ramos.codes/192.168.0.154"
"/immich.ramos.codes/192.168.0.154"
"/forgejo.ramos.codes/192.168.0.154"
"/frigate.ramos.codes/192.168.0.154"
];
server = [ "192.168.0.1" ];
};
};
services = {
pcscd.enable = gpgEnabled;
timesyncd = lib.mkDefault {
enable = true;
servers = [
"0.pool.ntp.org"
"1.pool.ntp.org"
"2.pool.ntp.org"
"3.pool.ntp.org"
];
};
pipewire = {
enable = true;
audio.enable = true;
wireplumber.enable = true;
pulse.enable = true;
jack.enable = true;
alsa.enable = true;
alsa.support32Bit = true;
};
openssh = {
enable = true;
startWhenNeeded = false;
settings = {
X11Forwarding = false;
PasswordAuthentication = false;
};
};
};
}

View file

@ -0,0 +1,20 @@
## Hardware
| Component | Model |
|-----------|--------------------------------|
| System | HP Z230 SFF Workstation |
| CPU | Intel Core i7-4770 @ 3.40GHz |
| GPU | Integrated |
| Storage | 6TB Seagate ST6000NM0024 |
| Network | Intel (onboard) |
## Memory
| Slot | Size | Manufacturer | Part Number | Speed |
|-------|------|---------------|-------------------|-----------|
| DIMM1 | 4GB | Hynix/Hyundai | HMT451U6AFR8C-PB | 1600 MT/s |
| DIMM2 | 4GB | Hynix/Hyundai | HMT451U6AFR8C-PB | 1600 MT/s |
| DIMM3 | 4GB | Hynix/Hyundai | HMT451U6AFR8C-PB | 1600 MT/s |
| DIMM4 | 4GB | Hynix/Hyundai | HMT451U6AFR8C-PB | 1600 MT/s |
**Total: 16GB DDR3**

View file

@ -0,0 +1,16 @@
{ inputs, ... }:
{
imports = [
inputs.disko.nixosModules.disko
(import ./modules/disko)
inputs.home-manager.nixosModules.home-manager
{ home-manager.sharedModules = [ inputs.sops-nix.homeManagerModules.sops ]; }
(import ./modules/home-manager)
../../../user
../../keys
../../modules/sops
./hardware.nix
./system.nix
];
}

View file

@ -0,0 +1,27 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot = {
initrd = {
availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usbhid" "sd_mod" "sr_mod" ];
kernelModules = [ ];
};
kernelModules = [ "kvm-intel" ];
extraModulePackages = [ ];
};
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
# Enable VAAPI for hardware video acceleration
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
intel-vaapi-driver # i965 driver for Haswell
];
};
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
}

View file

@ -0,0 +1,103 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.backup;
recipientArgs = concatMapStrings (r: "-r '${lib.strings.trim r}' ") cfg.recipients;
# Convert absolute paths to relative for tar, preserving structure
# e.g., /var/lib/forgejo -> var/lib/forgejo
tarPaths = map (p: removePrefix "/" p) cfg.paths;
excludeArgs = concatMapStrings (e: "--exclude='${e}' ") cfg.exclude;
backupScript = pkgs.writeShellScript "backup" ''
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_NAME="backup-$TIMESTAMP.tar.age"
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
echo "Starting backup: $BACKUP_NAME"
echo "Paths: ${concatStringsSep " " cfg.paths}"
export PATH="${pkgs.age-plugin-yubikey}/bin:$PATH"
${pkgs.gnutar}/bin/tar -C / ${excludeArgs}-cf - ${concatStringsSep " " tarPaths} | \
${pkgs.age}/bin/age ${recipientArgs} -o "$TEMP_DIR/$BACKUP_NAME"
${pkgs.rclone}/bin/rclone --config /root/.config/rclone/rclone.conf copy "$TEMP_DIR/$BACKUP_NAME" "${cfg.destination}"
# Prune old backups
${pkgs.rclone}/bin/rclone --config /root/.config/rclone/rclone.conf lsf "${cfg.destination}" | \
sort -r | \
tail -n +$((${toString cfg.keepLast} + 1)) | \
while read -r old; do
${pkgs.rclone}/bin/rclone --config /root/.config/rclone/rclone.conf delete "${cfg.destination}/$old"
done
echo "Backup complete"
'';
in
{
options.modules.system.backup = {
enable = mkEnableOption "Encrypted backups";
paths = mkOption {
type = types.listOf types.str;
default = [];
description = "Absolute paths to include in backup (structure preserved)";
};
exclude = mkOption {
type = types.listOf types.str;
default = [];
description = "Patterns to exclude (passed to tar --exclude)";
};
recipients = mkOption {
type = types.listOf types.str;
default = [];
description = "Age public keys for encryption";
};
destination = mkOption {
type = types.str;
default = "";
description = "Rclone destination";
};
schedule = mkOption {
type = types.str;
default = "daily";
description = "Systemd calendar expression";
};
keepLast = mkOption {
type = types.int;
default = 3;
description = "Number of backups to keep";
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.rclone ];
systemd.services.backup = {
description = "Encrypted backup";
serviceConfig = {
Type = "oneshot";
ExecStart = backupScript;
};
};
systemd.timers.backup = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.schedule;
Persistent = true;
};
};
};
}

View file

@ -0,0 +1,20 @@
server=1
rpccookiefile=/var/lib/bitcoin/.cookie
rpccookieperms=group
rpcbind=127.0.0.1
rpcallowip=127.0.0.1
dnsseed=0
onlynet=onion
bind=127.0.0.1
proxy=127.0.0.1:9050
listen=1
listenonion=1
torcontrol=127.0.0.1:9051
txindex=1
dbcache=1024

View file

@ -0,0 +1,80 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.bitcoin;
nginx = config.modules.system.nginx;
home = "/var/lib/bitcoin";
bitcoinConf = pkgs.writeTextFile {
name = "bitcoin.conf";
text = builtins.readFile ./config/bitcoin.conf;
};
in
{ options.modules.system.bitcoin = { enable = mkEnableOption "Bitcoin Server"; };
config = mkIf cfg.enable {
modules.system.tor.enable = true;
environment.systemPackages = with pkgs; [
bitcoind
];
users = {
users = {
"btc" = {
inherit home;
description = "Bitcoin Core system user";
isSystemUser = true;
group = "bitcoin";
extraGroups = [ "tor" ];
createHome = true;
};
"nginx" = {
extraGroups = mkIf nginx.enable [
"bitcoin"
];
};
};
groups = {
"bitcoin" = {
members = [
"btc"
config.user.name
];
};
};
};
programs.bash.shellAliases = {
btc = "bitcoin-cli";
};
services.bitcoind = {
"mainnet" = {
enable = true;
user = "btc";
group = "bitcoin";
configFile = bitcoinConf;
dataDir = home;
pidFile = "${home}/bitcoind.pid";
};
};
# Make data dir group-accessible so electrs/clightning can read cookie
systemd.tmpfiles.rules = [
"d ${home} 0750 btc bitcoin -"
];
systemd.services.bitcoind-mainnet = {
wants = [ "tor.service" ];
after = [ "tor.service" ];
serviceConfig.ExecStartPre = "+${pkgs.coreutils}/bin/chmod 750 /var/lib/tor";
};
modules.system.backup.paths = [
"${home}/wallets"
];
};
}

View file

@ -0,0 +1,31 @@
alias=OrdSux
network=bitcoin
bitcoin-datadir=/var/lib/bitcoin
bitcoin-rpcconnect=127.0.0.1
bitcoin-rpcport=8332
lightning-dir=/var/lib/clightning
plugin-dir=/var/lib/clightning/plugins
log-file=/var/lib/clightning/lightningd.log
log-level=info
rpc-file-mode=0660
# Bind RPC locally only
bind-addr=127.0.0.1:9736
# Auto-create Tor hidden service for peer connections
addr=autotor:127.0.0.1:9051
# Route outbound through Tor
proxy=127.0.0.1:9050
always-use-proxy=true
large-channels
fee-base=1000
fee-per-satoshi=10
min-capacity-sat=10000
htlc-minimum-msat=0
funding-confirms=3
max-concurrent-htlcs=30

View file

@ -0,0 +1,115 @@
{ lib, pkgs, config, ... }:
with lib;
let
cfg = config.modules.system.bitcoin.clightning;
btc = config.modules.system.bitcoin;
nginx = config.modules.system.nginx;
home = "/var/lib/clightning";
domain = "ramos.codes";
clnrest = pkgs.callPackage ./plugins/clnrest.nix { };
clnConfig = pkgs.writeTextFile {
name = "lightning.conf";
text = ''
${builtins.readFile ./config/lightning.conf}
bitcoin-cli=${pkgs.bitcoind}/bin/bitcoin-cli
# CLNRest configuration
clnrest-port=3010
clnrest-host=127.0.0.1
clnrest-protocol=https
'';
};
in
{ options.modules.system.bitcoin.clightning = { enable = mkEnableOption "Core Lightning Server"; };
config = mkIf (cfg.enable && btc.enable) {
environment.systemPackages = with pkgs; [
clightning
];
users = {
users = {
"clightning" = {
inherit home;
description = "Core Lightning system user";
isSystemUser = true;
group = "bitcoin";
extraGroups = [ "tor" ];
createHome = true;
};
};
groups = {
"bitcoin" = {
members = mkAfter [
"clightning"
];
};
};
};
programs.bash.shellAliases = {
cln = "lightning-cli";
};
systemd.services.lightningd = {
description = "Core Lightning Daemon";
wantedBy = [ "multi-user.target" ];
wants = [ "bitcoind-mainnet.service" "tor.service" ];
after = [
"bitcoind-mainnet.service"
"tor.service"
"network.target"
];
serviceConfig = {
ExecStartPre = "+${pkgs.coreutils}/bin/chmod 750 /var/lib/bitcoin /var/lib/tor ${home} ${home}/bitcoin";
ExecStart = "${pkgs.clightning}/bin/lightningd --conf=${clnConfig}";
User = "clightning";
Group = "bitcoin";
WorkingDirectory = home;
Type = "simple";
KillMode = "process";
TimeoutSec = 60;
Restart = "always";
RestartSec = 60;
};
};
# Bind mount from /data
fileSystems.${home} = {
device = "/data/clightning";
fsType = "none";
options = [ "bind" ];
};
# Ensure data directory exists with correct permissions
systemd.tmpfiles.rules = mkAfter [
"d /data/clightning 0750 clightning bitcoin -"
"d /data/clightning/bitcoin 0750 clightning bitcoin -"
"d /data/clightning/plugins 0750 clightning bitcoin -"
"L+ /home/${config.user.name}/.lightning - - - - ${home}"
"L+ ${home}/plugins/clnrest - - - - ${clnrest}/libexec/c-lightning/plugins/clnrest"
];
modules.system.backup.paths = [
"${home}/bitcoin/hsm_secret"
"${home}/bitcoin/emergency.recover"
];
services.nginx.virtualHosts."ln.${domain}" = mkIf nginx.enable {
useACMEHost = domain;
forceSSL = true;
locations."/" = {
proxyPass = "https://127.0.0.1:3010";
extraConfig = ''
proxy_ssl_verify off;
'';
};
};
};
}

View file

@ -0,0 +1,54 @@
{
lib,
rustPlatform,
fetchFromGitHub,
pkg-config,
openssl,
protobuf,
}:
rustPlatform.buildRustPackage rec {
pname = "clnrest";
version = "25.02.2";
src = fetchFromGitHub {
owner = "ElementsProject";
repo = "lightning";
rev = "v${version}";
hash = "sha256-SiPYB463l9279+zawsxmql1Ui/dTdah5KgJgmrWsR2A=";
};
cargoLock = {
lockFile = "${src}/Cargo.lock";
};
cargoBuildFlags = [
"-p"
"clnrest"
];
cargoTestFlags = [
"-p"
"clnrest"
];
nativeBuildInputs = [
pkg-config
protobuf
];
buildInputs = [ openssl ];
postInstall = ''
mkdir -p $out/libexec/c-lightning/plugins
mv $out/bin/clnrest $out/libexec/c-lightning/plugins/
rmdir $out/bin
'';
meta = {
description = "Transforms RPC calls into REST APIs";
homepage = "https://docs.corelightning.org/docs/rest";
license = lib.licenses.mit;
platforms = lib.platforms.linux;
mainProgram = "clnrest";
};
}

View file

@ -0,0 +1,13 @@
network = "bitcoin"
electrum_rpc_addr = "127.0.0.1:50001"
cookie_file = "/var/lib/bitcoin/.cookie"
db_dir = "/var/lib/electrs"
log_filters = "INFO"
daemon_rpc_addr = "127.0.0.1:8332"
daemon_p2p_addr = "127.0.0.1:8333"
daemon_dir = "/var/lib/bitcoin"

View file

@ -0,0 +1,121 @@
{ lib, pkgs, config, ... }:
with lib;
let
cfg = config.modules.system.bitcoin.electrum;
nginx = config.modules.system.nginx;
home = "/var/lib/electrs";
btc = config.modules.system.bitcoin;
domain = "ramos.codes";
electrsConfig = pkgs.writeTextFile {
name = "config.toml";
text = builtins.readFile ./config/config.toml;
};
in
{ options.modules.system.bitcoin.electrum = { enable = mkEnableOption "Electrs Server"; };
config = mkIf (cfg.enable && btc.enable) {
#TODO: Fix the failing overlay due to `cargoHash/cargoSha256`
#nixpkgs.overlays = [
# (final: prev: {
# electrs = prev.electrs.overrideAttrs (old: rec {
# pname = "electrs";
# version = "0.10.8";
# src = pkgs.fetchFromGitHub {
# owner = "romanz";
# repo = pname;
# rev = "v${version}";
# hash = "sha256-L26jzAn8vwnw9kFd6ciyYS/OLEFTbN8doNKy3P8qKRE=";
# };
# #cargoDeps = old.cargoDeps.overrideAttrs (const {
# # name = "electrs-${version}.tar.gz";
# # inherit src;
# # sha256 = "";
# #});
# cargoHash = "sha256-lBRcq73ri0HR3duo6Z8PdSjnC8okqmG5yWeHxH/LmcU=";
# });
# })
#];
environment.systemPackages = with pkgs; [
electrs
];
users = {
users = {
"electrs" = {
inherit home;
description = "Electrs system user";
isSystemUser = true;
group = "bitcoin";
createHome = true;
};
};
groups = {
"bitcoin" = {
members = mkAfter [
"electrs"
];
};
};
};
systemd.services.electrs = {
description = "Electrs Bitcoin Indexer";
wantedBy = [ "multi-user.target" ];
wants = [ "bitcoind-mainnet.service" ];
after = [
"bitcoind-mainnet.service"
"network.target"
];
serviceConfig = {
ExecStartPre = "+${pkgs.coreutils}/bin/chmod 750 /var/lib/bitcoin";
ExecStart = "${pkgs.electrs}/bin/electrs --conf=${electrsConfig}";
User = "electrs";
Group = "bitcoin";
WorkingDirectory = home;
Type = "simple";
KillMode = "process";
TimeoutSec = 60;
Restart = "always";
RestartSec = 60;
};
};
# Bind mount from /data
fileSystems.${home} = {
device = "/data/electrs";
fsType = "none";
options = [ "bind" ];
};
# Ensure db directory exists with correct permissions
systemd.tmpfiles.rules = [
"d /data/electrs 0750 electrs bitcoin -"
];
# Nginx SSL proxy for Electrum protocol (TCP)
networking.firewall.allowedTCPPorts = mkIf nginx.enable [ 50002 ];
services.nginx.streamConfig = mkIf nginx.enable ''
map $ssl_server_name $electrs_backend {
electrum.${domain} 127.0.0.1:50001;
default "";
}
server {
listen 50002 ssl;
proxy_pass $electrs_backend;
ssl_certificate /var/lib/acme/${domain}/fullchain.pem;
ssl_certificate_key /var/lib/acme/${domain}/key.pem;
}
'';
};
}

View file

@ -0,0 +1,35 @@
let
mkModules = dir: isRoot:
let
entries = builtins.readDir dir;
names = builtins.attrNames entries;
isModuleDir = path:
builtins.pathExists path &&
builtins.readFileType path == "directory" &&
builtins.baseNameOf path != "config" &&
builtins.baseNameOf path != "plugins" &&
builtins.baseNameOf path != "home-manager" &&
builtins.baseNameOf path != "disko";
isModule = file: file == "default.nix";
isNix = file: builtins.match ".*\\.nix" file != null && file != "default.nix";
in
builtins.concatMap (name:
let
path = "${dir}/${name}";
in
if isModuleDir path then
mkModules path false
else if isModule name && !isRoot then
[dir]
else if isNix name then
[path]
else
[]
) names;
in
{
imports = mkModules ./. true;
}

View file

@ -0,0 +1,75 @@
{ lib, ... }:
{
disko.devices = {
disk = {
main = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
lvm = {
size = "100%";
content = {
type = "lvm_pv";
vg = "vg0";
};
};
};
};
};
};
lvm_vg = {
vg0 = {
type = "lvm_vg";
lvs = {
root = {
size = "200G";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
data = {
size = "1T";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/data";
};
};
bitcoin = {
size = "1T";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/var/lib/bitcoin";
};
};
frigate = {
size = "3T";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/var/lib/frigate";
};
};
# ~300GB left unallocated for future growth
};
};
};
};
}

View file

@ -0,0 +1,100 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.forgejo;
nginx = config.modules.system.nginx;
domain = "ramos.codes";
socketPath = "/run/forgejo/forgejo.sock";
in
{
options.modules.system.forgejo = {
enable = mkEnableOption "Forgejo Server";
};
config = mkIf cfg.enable {
users.groups.git = {};
users.users.git = {
isSystemUser = true;
group = "git";
home = "/var/lib/forgejo";
shell = "${pkgs.bash}/bin/bash";
};
users.users.nginx = mkIf nginx.enable {
extraGroups = [ "git" ];
};
# Bind mount from /data
fileSystems."/var/lib/forgejo" = {
device = "/data/forgejo";
fsType = "none";
options = [ "bind" ];
};
systemd.tmpfiles.rules = [
"d /data/forgejo 0750 git git -"
"d /data/forgejo/.ssh 0700 git git -"
"d /data/forgejo/custom 0750 git git -"
"d /data/forgejo/data 0750 git git -"
];
services.forgejo = {
enable = true;
user = "git";
group = "git";
stateDir = "/var/lib/forgejo";
settings = {
DEFAULT = {
APP_NAME = "Git Server";
APP_SLOGAN = "";
};
service.REQUIRE_SIGNIN_VIEW = false;
server = {
DOMAIN = "git.${domain}";
ROOT_URL = "https://git.${domain}/";
PROTOCOL = "http+unix";
HTTP_ADDR = socketPath;
SSH_DOMAIN = "git.${domain}";
SSH_PORT = 22;
START_SSH_SERVER = false;
LANDING_PAGE = "explore";
};
service = {
REGISTER_MANUAL_CONFIRM = true;
DISABLE_REGISTRATION = false;
DEFAULT_ALLOW_CREATE_ORGANIZATION = false;
};
admin = {
DISABLE_REGULAR_ORG_CREATION = true;
};
auth = {
ENABLE_BASIC_AUTHENTICATION = true;
};
};
database = {
type = "sqlite3";
path = "/var/lib/forgejo/data/forgejo.db";
};
};
modules.system.backup.paths = [
"/var/lib/forgejo"
];
services.nginx.virtualHosts."git.${domain}" = mkIf nginx.enable {
useACMEHost = domain;
forceSSL = true;
locations."/" = {
proxyPass = "http://unix:${socketPath}";
};
};
};
}

View file

@ -0,0 +1,162 @@
# Frigate Camera Setup
## Camera Models
| Camera | Model | MAC | IP |
|--------|-------|-----|-----|
| cam4 | W461ASC | 00:1f:54:c2:d1:b1 | 192.168.1.194 |
| cam1 | B463AJ | 00:1f:54:a9:81:d1 | 192.168.1.167 |
| cam2 | W463AQ (ch1) | 00:1f:54:b2:9b:1d | 192.168.1.147 |
| cam3 | W463AQ (ch2) | 00:1f:54:b2:9b:1d | 192.168.1.147 |
| cam5 | SL300 | | | |
## Network Architecture
- Camera network: 192.168.1.0/24 (isolated, no internet)
- Server NIC: enp2s0f1 @ 192.168.1.1
- WiFi AP: TP-Link RE315 @ 192.168.1.254
- DHCP range: 192.168.1.100-200
## RTSP URL Format
```
rtsp://admin:ocu?u3Su@<IP>/cam/realmonitor?channel=<CH>&subtype=0
```
- channel=1 for single-camera devices
- channel=1,2 for dual-camera devices (W463AQ)
- subtype=0 for main stream, subtype=1 for sub stream
## Camera Reset Procedures
### W461ASC (cam4)
1. Keep camera powered on
2. Reset button is on the back of the camera
3. Press and hold reset button for 30-60 seconds until chime sounds
### B463AJ (cam1)
1. Remove doorbell from mount
2. Locate reset button on the back
3. Press and hold until you hear chime reset sound
4. Reconnect via Lorex app as new device
### W463AQ (cam2/cam3)
1. Keep camera powered on
2. Rotate the lens upwards to reveal hidden reset button
3. Press and hold reset button until you hear audio prompt
4. Flashing green Smart Security Lighting confirms reset
5. Solid green = not fully reset, repeat if needed
### SL300 (cam5)
1. Keep camera powered on
2. Tilt camera lens upwards to reveal reset/microSD card cover
3. Remove the cover
4. Press and hold reset button until audio prompt
5. Replace cover quickly
6. Wait for green LED flash + audio confirmation
## Initial Setup
1. Temporarily enable internet for camera network:
```bash
sudo iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o enp2s0f0 -j MASQUERADE
sudo sysctl -w net.ipv4.ip_forward=1
```
2. Connect camera to "cams" WiFi network
3. Use Lorex app to configure camera (requires cloud - CCP middleman)
4. Get camera MAC from DHCP leases:
```bash
cat /var/lib/dnsmasq/dnsmasq.leases
```
5. Add DHCP reservation in `system.nix`:
```nix
dhcp-host = [
"aa:bb:cc:dd:ee:ff,192.168.1.XXX,camera_name"
];
```
6. Add MAC to firewall block list in `system.nix`:
```nix
iptables -A FORWARD -m mac --mac-source aa:bb:cc:dd:ee:ff -j DROP
```
7. Update camera IP in `frigate/default.nix` and enable
8. Deploy and disable internet:
```bash
nixos-rebuild switch --flake .#server --target-host server
sudo iptables -t nat -D POSTROUTING -s 192.168.1.0/24 -o enp2s0f0 -j MASQUERADE
sudo sysctl -w net.ipv4.ip_forward=0
```
## Storage
| Path | Bind Mount | Contents |
|------|------------|----------|
| /var/lib/frigate | /data/frigate/lib | Database, recordings, clips |
## Notes
- Lorex cameras are cloud-only for configuration (no local web UI responds)
- RTSP works locally without internet
- Cameras phone home aggressively when internet is available - keep isolated
- Haswell CPU cannot hardware decode HEVC - using CPU decode
- Consider T400 GPU for hardware acceleration if scaling to more cameras
## Port Scan Results (W461ASC)
- 80/tcp - HTTP (non-responsive, proprietary)
- 554/tcp - RTSP (working)
- 8086/tcp - Proprietary
- 35000/tcp - Proprietary
## Planned Upgrades
Replace Lorex cameras with proper RTSP/ONVIF cameras for reliable Frigate integration.
| Current | Replacement | Price | Notes |
|---------|-------------|-------|-------|
| cam1 (B861AJ) | Reolink Video Doorbell WiFi | ~$120 | 5MP, wired power + WiFi, always-on |
| cam4 (W461ASC) | TP-Link Tapo C110 | ~$30 | 3MP, compact, window-friendly |
| cam2 + cam3 (W463AQ) | Reolink E1 Pro | ~$45 | 4MP, 355° pan |
| cam5 (SL300) | **Remove** | - | Obstructed, overlaps with cam4 |
**Total: ~$195**
### Reolink Video Doorbell WiFi
- URL: https://reolink.com/us/product/reolink-video-doorbell-wifi
- Model: SKU 2267808
- Resolution: 5MP (2560x1920 @ 20fps)
- Dimensions: Standard doorbell form factor
- Power: Hardwired 12-24VAC or DC 24V (always-on, no battery)
- Network: 2.4GHz/5GHz WiFi
- Protocols: RTSP, ONVIF, RTMP, HTTPS
- FOV: 180° diagonal (135° H, 100° V)
### TP-Link Tapo C110
- URL: https://www.tp-link.com/us/home-networking/cloud-camera/tapo-c110/
- Resolution: 3MP (2304x1296 @ 15fps)
- Dimensions: 2.66" x 2.15" x 3.89" (compact cube, similar to Lorex W461ASC)
- Power: 9V DC adapter
- Network: 2.4GHz WiFi
- Protocols: RTSP, ONVIF (officially supported NVR mode)
- RTSP URL: `rtsp://user:pass@IP:554/stream1` (main), `stream2` (sub)
- Frigate: Confirmed working - https://www.simonam.dev/tapo-c110-frigate-config/
### Reolink E1 Pro
- URL: https://reolink.com/us/product/e1-pro/
- Resolution: 4MP (2560x1440)
- Dimensions: ~4" dome with pan/tilt
- Power: 5V DC adapter
- Network: 2.4GHz/5GHz WiFi
- Protocols: RTSP, ONVIF
- Features: Pan 355°, Tilt 50°, person/pet detection
**Why replace Lorex:** Cloud-dependent config, no ONVIF, doorbell sleeps on battery, aggressive phone-home behavior requires network isolation.

View file

@ -0,0 +1,294 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.frigate;
nginx = config.modules.system.nginx;
domain = "ramos.codes";
user = config.sops.placeholder."RTSP_USER";
pass = config.sops.placeholder."RTSP_PASS";
in
{
options.modules.system.frigate = {
enable = mkEnableOption "Enable Frigate NVR";
};
config = mkIf cfg.enable {
# Allow user to access frigate recordings via SSHFS
users.users.${config.user.name}.extraGroups = [ "frigate" ];
# go2rtc config with credentials from sops
sops.templates."go2rtc.yaml" = {
content = ''
rtsp:
listen: ":8554"
webrtc:
listen: ":8555"
streams:
cam1: "rtsp://${user}:${pass}@192.168.1.167/cam/realmonitor?channel=1&subtype=0#backchannel=1"
cam1_sub: "rtsp://${user}:${pass}@192.168.1.167/cam/realmonitor?channel=1&subtype=1"
cam2: "rtsp://${user}:${pass}@192.168.1.147/cam/realmonitor?channel=1&subtype=0#backchannel=1"
cam2_sub: "rtsp://${user}:${pass}@192.168.1.147/cam/realmonitor?channel=1&subtype=1"
cam3: "rtsp://${user}:${pass}@192.168.1.147/cam/realmonitor?channel=2&subtype=0#backchannel=1"
cam3_sub: "rtsp://${user}:${pass}@192.168.1.147/cam/realmonitor?channel=2&subtype=1"
cam4: "rtsp://${user}:${pass}@192.168.1.194/cam/realmonitor?channel=1&subtype=0"
cam4_sub: "rtsp://${user}:${pass}@192.168.1.194/cam/realmonitor?channel=1&subtype=1"
'';
mode = "0444"; # go2rtc runs as dynamic user, needs read access
};
# go2rtc service using sops-templated config
services.go2rtc.enable = true;
systemd.services.go2rtc = {
serviceConfig.ExecStart = mkForce "${pkgs.go2rtc}/bin/go2rtc -config ${config.sops.templates."go2rtc.yaml".path}";
after = [ "sops-nix.service" ];
wants = [ "sops-nix.service" ];
};
services.frigate = {
enable = true;
package = pkgs.unstable.frigate;
hostname = "frigate.${domain}";
vaapiDriver = "i965"; # Haswell iGPU for H.264 decode
settings = {
mqtt.enabled = false;
ffmpeg = {
hwaccel_args = "preset-vaapi"; # VAAPI for H.264 substream detection
input_args = "preset-rtsp-restream"; # TCP transport for go2rtc
};
birdseye = {
mode = "continuous";
width = 1280;
height = 720;
quality = 8; # 8 - 31
};
motion = {
enabled = true;
};
detect = {
enabled = true;
min_initialized = 3;
max_disappeared = 25;
width = 1280;
height = 720;
};
audio = {
enabled = true;
max_not_heard = 30;
min_volume = 600;
listen = [
"glass"
"shatter"
"fire_alarm"
"boom"
"thump"
"siren"
"alarm"
"explosion"
"burst"
];
};
audio_transcription = {
enabled = true;
model_size = "small";
language = "en";
};
record = {
enabled = true;
continuous.days = 3; # Full 24/7 footage
motion.days = 7; # Motion segments after continuous expires
detections.retain = {
days = 14; # Any tracked object (person, car, etc.)
mode = "motion";
};
alerts.retain = {
days = 30; # Zone violations, loitering - important stuff
mode = "all";
};
};
snapshots = {
enabled = true;
retain = {
default = 3;
};
quality = 80;
};
cameras = {
cam1 = {
enabled = true;
ffmpeg.inputs = [
{
path = "rtsp://127.0.0.1:8554/cam1";
roles = [ "record" ];
}
{
path = "rtsp://127.0.0.1:8554/cam1_sub";
roles = [ "detect" "audio" ];
}
];
};
cam2 = {
enabled = true;
motion.enabled = false;
detect.enabled = false;
objects.mask = [ "0.969,0.078,0.846,0.075,0.845,0.034,0.97,0.037" ];
ffmpeg.inputs = [
{
path = "rtsp://127.0.0.1:8554/cam2";
roles = [ "record" ];
}
{
path = "rtsp://127.0.0.1:8554/cam2_sub";
roles = [ "detect" "audio" ];
}
];
};
cam3 = {
enabled = true;
motion.enabled = false;
detect.enabled = false;
ffmpeg.inputs = [
{
path = "rtsp://127.0.0.1:8554/cam3";
roles = [ "record" ];
}
{
path = "rtsp://127.0.0.1:8554/cam3_sub";
roles = [ "detect" "audio" ];
}
];
};
cam4 = {
enabled = true;
audio.enabled = false;
motion.mask = [ "0.811,0.109,0.954,0.111,0.959,0.065,0.811,0.055" ];
zones.zone1 = {
friendly_name = "lot";
coordinates = "0.299,0.438,0.191,0.951,0.453,0.964,0.453,0.437";
loitering_time = 10;
};
ffmpeg.inputs = [
{
path = "rtsp://127.0.0.1:8554/cam4";
roles = [ "record" ];
}
{
path = "rtsp://127.0.0.1:8554/cam4_sub";
roles = [ "detect" ];
}
];
};
};
classification = {
custom = {
"door" = {
enabled = true;
name = "door";
threshold = 0.8;
state_config = {
cameras = {
cam2.crop = [
0.8595647692717828
0.39901413156128707
0.9903488513256276
0.6315191663236775
];
cam3.crop = [
0.0008617338314475493
0.3909394833748086
0.12040036569190293
0.6034526066822848
];
};
motion = true;
};
};
"lot" = {
enabled = true;
name = "lot";
threshold = 0.8;
state_config = {
cameras = {
cam4.crop = [
0.2757899560295573
0.5156825410706086
0.4445399560295573
0.8156825410706086
];
};
motion = true;
};
};
};
};
};
};
# Add SSL to frigate's nginx virtualHost
services.nginx.virtualHosts."frigate.${domain}" = mkIf nginx.enable {
useACMEHost = domain;
forceSSL = true;
locations."/go2rtc/" = {
proxyPass = "http://127.0.0.1:1984/";
proxyWebsockets = true;
};
};
# Frigate segment cache in RAM (reduces disk writes)
fileSystems."/var/cache/frigate" = {
device = "tmpfs";
fsType = "tmpfs";
options = [ "size=512M" "mode=0755" ];
};
systemd.tmpfiles.rules = [
# Set ownership after tmpfs mount
"d /var/cache/frigate 0750 frigate frigate -"
# Create log directories for Frigate API (NixOS uses journald, but API expects these)
"d /dev/shm/logs 0755 frigate frigate -"
"d /dev/shm/logs/frigate 0755 frigate frigate -"
"d /dev/shm/logs/nginx 0755 frigate frigate -"
"d /dev/shm/logs/go2rtc 0755 frigate frigate -"
];
# Pipe journald logs to files for Frigate GUI
systemd.services.frigate-log-pipe = {
description = "Pipe logs to /dev/shm for Frigate GUI";
wantedBy = [ "multi-user.target" ];
after = [ "frigate.service" "go2rtc.service" "nginx.service" ];
serviceConfig = {
Type = "simple";
Restart = "always";
ExecStart = pkgs.writeShellScript "frigate-log-pipe" ''
while true; do
${pkgs.systemd}/bin/journalctl -u frigate -n 500 -o cat > /dev/shm/logs/frigate/current 2>/dev/null
${pkgs.systemd}/bin/journalctl -u go2rtc -n 500 -o cat > /dev/shm/logs/go2rtc/current 2>/dev/null
${pkgs.systemd}/bin/journalctl -u nginx -n 500 -o cat > /dev/shm/logs/nginx/current 2>/dev/null
chown frigate:frigate /dev/shm/logs/*/current
sleep 5
done
'';
};
};
# Backup recordings/database
modules.system.backup = {
paths = [ "/var/lib/frigate" ];
};
};
}

View file

@ -0,0 +1,23 @@
{ config, ... }:
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.${config.user.name} = {
imports = [
../../../../../user
../../../../../user/home.nix
../../../../../user/modules
];
home.stateVersion = "25.11";
# Machine-specific modules
modules.user = {
neovim.enable = false;
vim.enable = true;
tmux.enable = false;
utils.dev.enable = true;
};
};
}

View file

@ -0,0 +1,57 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.immich;
nginx = config.modules.system.nginx;
domain = "ramos.codes";
port = 2283;
in
{
options.modules.system.immich = {
enable = mkEnableOption "Immich Photo Server";
};
config = mkIf cfg.enable {
# Bind mount from /data
systemd.tmpfiles.rules = [
"d /data/immich 0750 immich immich -"
"d /data/postgresql 0750 postgres postgres -"
];
fileSystems."/var/lib/immich" = {
device = "/data/immich";
fsType = "none";
options = [ "bind" ];
};
fileSystems."/var/lib/postgresql" = {
device = "/data/postgresql";
fsType = "none";
options = [ "bind" ];
};
services.immich = {
enable = true;
port = port;
host = "127.0.0.1";
mediaLocation = "/var/lib/immich";
machine-learning.enable = false;
};
modules.system.backup.paths = [
"/var/lib/immich"
"/var/lib/postgresql"
];
services.nginx.virtualHosts."photos.${domain}" = mkIf nginx.enable {
useACMEHost = domain;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
proxyWebsockets = true;
};
};
};
}

View file

@ -0,0 +1,76 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.nginx;
domain = "ramos.codes";
in
{
options.modules.system.nginx = {
enable = mkEnableOption "Nginx Reverse Proxy";
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [ 80 443 ];
systemd.services.nginx.serviceConfig.LimitNOFILE = 65536;
security.acme = {
acceptTerms = true;
defaults.email = config.user.email;
certs."${domain}" = {
domain = "*.${domain}";
dnsProvider = "namecheap";
environmentFile = "/var/lib/acme/namecheap.env";
group = "nginx";
};
};
services.sslh = {
enable = true;
listenAddresses = [ "0.0.0.0" ];
port = 443;
settings = {
protocols = [
{ name = "ssh"; host = "127.0.0.1"; port = "22"; }
{ name = "tls"; host = "127.0.0.1"; port = "4443"; }
];
};
};
services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
eventsConfig = "worker_connections 4096;";
defaultSSLListenPort = 4443;
# Catch-all default - friendly error for unknown subdomains
virtualHosts."_" = {
default = true;
useACMEHost = domain;
forceSSL = true;
locations."/" = {
return = "404 'Not Found: This subdomain does not exist.'";
extraConfig = ''
add_header Content-Type text/plain;
'';
};
};
virtualHosts."test.${domain}" = {
useACMEHost = domain;
forceSSL = true;
locations."/" = {
return = "200 'nginx is working'";
extraConfig = ''
add_header Content-Type text/plain;
'';
};
};
};
};
}

View file

@ -0,0 +1,30 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.tor;
in
{
options.modules.system.tor = {
enable = mkEnableOption "Tor";
};
config = mkIf cfg.enable {
services.tor = {
enable = true;
client = {
enable = true;
# SOCKS proxy on 127.0.0.1:9050
};
settings = {
ControlPort = 9051;
CookieAuthentication = true;
CookieAuthFileGroupReadable = true;
DataDirectoryGroupReadable = true;
};
};
};
}

View file

@ -0,0 +1,69 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.webdav;
domain = "ramos.codes";
in
{
options.modules.system.webdav = {
enable = mkEnableOption "WebDAV server for phone backups";
directory = mkOption {
type = types.path;
default = "/var/lib/seedvault";
description = "Directory to store backups";
};
};
config = mkIf cfg.enable {
# Create backup directory
systemd.tmpfiles.rules = [
"d ${cfg.directory} 0750 webdav webdav -"
];
services.webdav = {
enable = true;
# Credentials in /var/lib/webdav/env:
# WEBDAV_USERNAME=seedvault
# WEBDAV_PASSWORD=your-secure-password
environmentFile = "/var/lib/webdav/env";
settings = {
address = "127.0.0.1";
port = 8090;
directory = cfg.directory;
behindProxy = true;
permissions = "CRUD"; # Create, Read, Update, Delete
users = [
{
username = "{env}WEBDAV_USERNAME";
password = "{env}WEBDAV_PASSWORD";
}
];
};
};
services.nginx.virtualHosts."backup.${domain}" = {
useACMEHost = domain;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8090";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebDAV needs these
proxy_pass_request_headers on;
proxy_set_header Destination $http_destination;
# Large file uploads for backups
client_max_body_size 0;
proxy_request_buffering off;
'';
};
};
};
}

View file

@ -0,0 +1,192 @@
{ pkgs, lib, config, ... }:
{ system.stateVersion = "25.11";
imports = [ ./modules ];
modules.system.sops.enable = true;
# Camera RTSP credentials (used by frigate/go2rtc)
sops.secrets = let cameras = { sopsFile = ../../../secrets/system/cameras.yaml; }; in {
"RTSP_USER" = cameras;
"RTSP_PASS" = cameras;
};
modules.system = {
nginx.enable = true;
forgejo.enable = true;
frigate.enable = true;
immich.enable = true;
webdav.enable = false;
# bitcoin = {
# enable = true;
# electrum.enable = true;
# clightning.enable = true;
# };
backup = {
enable = true;
recipients = [
"${config.user.keys.age.yubikey}"
"${config.machines.keys.desktop.ssh}"
];
paths = [ "/root/.config/rclone" ];
destination = "gdrive:backups/server";
schedule = "daily";
keepLast = 2;
};
};
users.users = {
${config.user.name} = {
isNormalUser = true;
extraGroups = config.user.groups;
openssh.authorizedKeys.keys = [
"${config.machines.keys.desktop.ssh}"
];
};
};
nix = {
channel.enable = false;
package = pkgs.nixVersions.stable;
extraOptions = "experimental-features = nix-command flakes";
settings = {
auto-optimise-store = true;
trusted-users = [ "${config.user.name}" ];
};
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
boot.loader = {
timeout = 3;
grub = {
enable = true;
devices = [ "nodev" ];
efiSupport = true;
configurationLimit = 5;
splashImage = null;
};
efi = {
canTouchEfiVariables = true;
};
};
environment.systemPackages = with pkgs; [
wget
git
vim
htop
];
security.sudo = {
wheelNeedsPassword = false;
execWheelOnly = true;
};
time = {
timeZone = "America/New_York";
hardwareClockInLocalTime = true;
};
services.timesyncd = lib.mkDefault {
enable = true;
servers = [
"0.pool.ntp.org"
"1.pool.ntp.org"
"2.pool.ntp.org"
"3.pool.ntp.org"
];
};
i18n.defaultLocale = "en_US.UTF-8";
console.font = "Lat2-Terminus16";
networking = {
hostName = "server";
useDHCP = false;
interfaces.enp2s0f0 = {
ipv4.addresses = [{
address = "192.168.0.154";
prefixLength = 24;
}];
};
# Camera network - isolated, no gateway
interfaces.enp2s0f1 = {
ipv4.addresses = [{
address = "192.168.1.1";
prefixLength = 24;
}];
};
defaultGateway = "192.168.0.1";
nameservers = [ "1.1.1.1" "8.8.8.8" ];
firewall = {
enable = true;
allowedTCPPorts = [ 22 ];
allowedUDPPorts = [ 53 67 ]; # DNS + DHCP
extraCommands = ''
# Block camera MACs from forwarding (instant DROP, no timeouts)
iptables -A FORWARD -m mac --mac-source 00:1f:54:c2:d1:b1 -j DROP # cam4
iptables -A FORWARD -m mac --mac-source 00:1f:54:b2:9b:1d -j DROP # cam2/cam3
iptables -A FORWARD -m mac --mac-source 00:1f:54:a9:81:d1 -j DROP # cam1
'';
extraStopCommands = ''
iptables -D FORWARD -m mac --mac-source 00:1f:54:c2:d1:b1 -j DROP || true
iptables -D FORWARD -m mac --mac-source 00:1f:54:b2:9b:1d -j DROP || true
iptables -D FORWARD -m mac --mac-source 00:1f:54:a9:81:d1 -j DROP || true
'';
};
};
services.dnsmasq = {
enable = true;
settings = {
# All *.ramos.codes subdomains -> local server
address = "/.ramos.codes/192.168.0.154";
# Except www, http, https and bare domain -> forward to upstream
server = [
"/www.ramos.codes/1.1.1.1"
"/http.ramos.codes/1.1.1.1"
"/https.ramos.codes/1.1.1.1"
"/ramos.codes/1.1.1.1"
"1.1.1.1"
"8.8.8.8"
];
cache-size = 1000;
# Camera network DHCP (isolated - no gateway = no internet)
interface = "enp2s0f1";
bind-interfaces = true;
dhcp-range = "192.168.1.100,192.168.1.200,24h";
# Static DHCP reservations for cameras
dhcp-host = [
"00:1f:54:c2:d1:b1,192.168.1.194,cam4"
"00:1f:54:b2:9b:1d,192.168.1.147,cam2"
"00:1f:54:a9:81:d1,192.168.1.167,cam1"
];
};
};
services.fail2ban = {
enable = true;
maxretry = 5;
bantime = "1h";
};
services.openssh = {
enable = true;
startWhenNeeded = true;
settings = {
X11Forwarding = false;
PasswordAuthentication = false;
PermitRootLogin = "no";
};
};
}

View file

@ -0,0 +1,15 @@
{ inputs, ... }:
{
imports = [
inputs.nixos-wsl.nixosModules.wsl
(import ./modules/wsl)
inputs.home-manager.nixosModules.home-manager
{ home-manager.sharedModules = [ inputs.sops-nix.homeManagerModules.sops ]; }
(import ./modules/home-manager)
../../../user
../../keys
../../modules/sops
./system.nix
];
}

View file

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

View file

@ -0,0 +1,24 @@
{ config, ... }:
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.${config.user.name} = {
imports = [
../../../../../user
../../../../../user/home.nix
../../../../../user/modules
];
home.stateVersion = "23.11";
# Machine-specific modules
modules.user = {
utils = {
dev.enable = true;
email.enable = true;
irc.enable = true;
};
};
};
}

View file

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

View file

@ -0,0 +1,20 @@
{ config, lib, ... }:
{
imports = [ ../../../../../user ];
wsl = rec {
enable = true;
defaultUser = lib.mkDefault config.user.name;
useWindowsDriver = true;
wslConf = {
user.default = lib.mkDefault defaultUser;
boot.command = "cd";
network = {
hostname = "${config.networking.hostName}";
generateHosts = true;
};
};
};
}

View file

@ -0,0 +1,75 @@
{ pkgs, lib, config, ... }:
{
system.stateVersion = "23.11";
boot.isContainer = true;
users.users = {
${config.user.name} = {
isNormalUser = true;
extraGroups = config.user.groups;
openssh.authorizedKeys.keys = [
"${config.user.keys.ssh.yubikey}"
];
};
};
nix = {
channel.enable = false;
package = pkgs.nixVersions.stable;
extraOptions = ''
experimental-features = nix-command flakes
'';
settings = {
auto-optimise-store = true;
trusted-users = [ "${config.user.name}" ];
};
gc = {
automatic = true;
dates = "daily";
options = "--delete-older-than 7d";
};
};
security.sudo = {
wheelNeedsPassword = false;
execWheelOnly = true;
};
time = {
timeZone = "America/New_York";
};
i18n.defaultLocale = "en_US.UTF-8";
console = {
font = "Lat2-Terminus16";
useXkbConfig = true;
};
networking = {
hostName = "wsl";
useDHCP = lib.mkDefault true;
firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
};
};
services = {
openssh = {
enable = true;
startWhenNeeded = true;
settings = {
X11Forwarding = false;
PasswordAuthentication = false;
};
};
timesyncd = lib.mkDefault {
enable = true;
servers = [
"time.windows.com"
];
};
};
}

View file

@ -0,0 +1,31 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.sops;
in
{
options.modules.system.sops = { enable = mkEnableOption "Enable sops-nix"; };
config = mkIf cfg.enable {
# Smartcard daemon for Yubikey (GPG, etc.)
services.pcscd.enable = true;
services.udev.packages = [ pkgs.yubikey-personalization ];
environment.systemPackages = with pkgs; [
age
sops
];
# Per-machine age key for system secrets (boot-time, unattended)
# This is the sops-nix default path
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
# Symlink for root so `sudo sops` finds the key automatically
systemd.tmpfiles.rules = [
"d /root/.config/sops/age 0700 root root -"
"L+ /root/.config/sops/age/keys.txt - - - - /var/lib/sops-nix/key.txt"
];
};
}

323
user/bookmarks/default.nix Normal file
View file

@ -0,0 +1,323 @@
[
{
name = "toolbar";
toolbar = true;
bookmarks = [
{
name = "ArchWiki";
url = "https://wiki.archlinux.org/";
tags = [ "dev" "linux" "docs" ];
}
{
name = "NixOS";
bookmarks = [
{
name = "Nixpkgs";
url = "https://search.nixos.org/packages";
tags = [ "nix" "dev" "linux" ];
keyword = "pkgs";
}
{
name = "Home Manager";
bookmarks = [
{
name = "Home Manager Option Docs";
url = "https://nix-community.github.io/home-manager/options.xhtml";
tags = [ "nix" "dev" "linux" ];
keyword = "hm";
}
{
name = "Home Manager Option Search";
url = "https://home-manager-options.extranix.com";
tags = [ "nix" "dev" "linux" ];
keyword = "hm";
}
];
}
{
name = "Nix Docs";
bookmarks = [
{
name = "nix.dev";
url = "https://nix.dev";
tags = [ "nix" "dev" "docs" ];
keyword = "nix";
}
];
}
];
}
{
name = "Gentoo Wiki";
url = "https://wiki.gentoo.org";
tags = [ "dev" "linux" "docs" ];
keyword = "gentoo";
}
{
name = "Email";
bookmarks = [
{
name = "Gmail";
url = "https://gmail.com";
tags = [ "google" "email" ];
keyword = "gmail";
}
{
name = "ProtonMail";
url = "https://mail.protonmail.com";
tags = [ "email" "personal" "work" ];
keyword = "email";
}
];
}
{
name = "Work";
bookmarks = [
{
name = "Outlook";
url = "https://outlook.office365.com";
tags = [ "work" "email" "microsoft" ];
keyword = "work";
}
{
name = "Teams";
url = "https://teams.microsoft.com";
tags = [ "work" "microsoft" ];
keyword = "teams";
}
];
}
{
name = "Youtube";
url = "https://youtube.com";
tags = [ "social" "entertainment" "google" ];
keyword = "youtube";
}
{
name = "Substack";
url = "https://substack.com";
tags = [ "social" "blogging" "personal" ];
keyword = "blog";
}
{
name = "Amazon";
url = "https://amazon.com";
tags = [ "shopping" "amazon" ];
keyword = "amazon";
}
{
name = "Social";
bookmarks = [
{
name = "Twitter";
url = "https://x.com";
tags = [ "social" "forum" ];
keyword = "x";
}
{
name = "Reddit";
url = "https://reddit.com";
tags = [ "social" "forum" ];
keyword = "reddit";
}
{
name = "Twitch";
url = "https://twitch.com";
tags = [ "social" "entertainment" "amazon" ];
keyword = "twitch";
}
{
name = "Nostr";
url = "https://primal.net";
tags = [ "social" "nostr" "bitcoin" ];
keyword = "nostr";
}
];
}
{
name = "ChatGPT";
url = "https://chat.openai.com";
tags = [ "dev" "ai" "microsoft" ];
keyword = "ai";
}
{
name = "Dev";
bookmarks = [
{
name = "Github";
url = "https://github.com";
tags = [ "dev" "work" "personal" "microsoft" ];
keyword = "git";
}
{
name = "Gist";
url = "https://gist.github.com";
tags = [ "dev" "work" "personal" "microsoft" "blogging" ];
keyword = "gist";
}
{
name = "Stack Overflow";
url = "https://stackoverflow.com";
tags = [ "dev" "work" "forum" ];
}
{
name = "Learning";
bookmarks = [
{
name = "Coding";
bookmarks = [
{
name = "Leetcode";
url = "https://leetcode.com";
}
{
name = "CodeWars";
url = "https://codewars.com";
}
];
}
{
name = "Projects";
bookmarks = [
{
name = "Linux From Scratch";
url = "https://linuxfromscratch.org/lfs/view/stable/index.html";
}
];
}
];
}
{
name = "Documentation";
bookmarks = [
{
name = "MDN";
url = "https://developer.mozilla.org";
tags = [ "dev" "docs" ];
keyword = "mdn";
}
{
name = "DevDocs";
url = "https://devdocs.io";
tags = [ "dev" "docs" ];
keyword = "docs";
}
{
name = "Linux Kernel";
url = "https://docs.kernel.org";
}
{
name = "References";
bookmarks = [
{
name = "ASCII Table";
url = "https://asciitable.com";
tags = [ "dev" ];
keyword = "ascii";
}
{
name = "Regex Cheat Sheet";
url = "https://rexegg.com/regex-quickstart.php";
tags = [ "dev" ];
keyword = "regex";
}
];
}
];
}
{
name = "Tools";
bookmarks = [
{
name = "GitBook";
url = "https://gitbook.com";
tags = [ "dev" "docs" ];
}
{
name = "Namecheap";
url = "https://namecheap.com";
tags = [ "dev" "shopping" "hosting" ];
}
{
name = "LetsEncrypt";
url = "https://letsencrypt.com";
tags = [ "dev" "hosting" ];
}
{
name = "Gitea";
url = "https://gitea.com";
tags = [ "dev" "hosting" ];
}
{
name = "Hosting";
bookmarks = [
{
name = "DigitalOcean";
url = "https://digitalocean.com";
tags = [ "dev" "hosting" ];
}
{
name = "Supabase";
url = "https://supabase.com";
tags = [ "dev" "hosting" ];
}
{
name = "Vercel";
url = "https://vercel.com";
tags = [ "dev" "hosting" ];
}
{
name = "AWS";
url = "https://aws.amazon.com";
tags = [ "dev" "hosting" ];
}
{
name = "Azure";
url = "https://azure.microsoft.com";
tags = [ "dev" "hosting" ];
}
{
name = "Firebase";
url = "https://firebase.google.com";
tags = [ "dev" "hosting" ];
}
];
}
];
}
];
}
{
name = "Financials";
bookmarks = [
{
name = "Fidelity";
url = "https://fidelity.com";
tags = [ "banking" ];
keyword = "bank";
}
{
name = "Chase";
url = "https://chase.com";
tags = [ "banking" ];
}
{
name = "Wells Fargo";
url = "https://wellsfargo.com";
tags = [ "banking" ];
}
{
name = "Crapto";
bookmarks = [
{
name = "Coinbase";
url = "https://coinbase.com";
tags = [ "banking" ];
}
];
}
];
}
];
}
]

View file

@ -0,0 +1 @@
AGE-PLUGIN-YUBIKEY-1C8CXVQVZUK7AV2CFWEKFP

20
user/default.nix Normal file
View file

@ -0,0 +1,20 @@
{ lib, pkgs, ... }:
with lib;
{
options = {
user = mkOption {
description = "User Configurations";
type = types.attrs;
default = with pkgs; rec {
name = "bryan";
email = "bryan@ramos.codes";
shell = bash;
keys = import ./keys { inherit lib; };
groups = [ "wheel" "networkmanager" "home-manager" "input" ];
bookmarks = import ./bookmarks;
};
};
};
}

39
user/home.nix Normal file
View file

@ -0,0 +1,39 @@
{ lib, pkgs, config, ... }:
let
pass = pkgs.pass.withExtensions (exts: with exts; [
pass-audit
pass-otp
pass-update
]);
in
{
programs.home-manager.enable = true;
home.username = config.user.name;
home.homeDirectory = "/home/${config.user.name}";
# Essential packages for all users
home.packages = with pkgs; [
pass
wget curl fastfetch fd
unzip zip rsync
calc calcurse
age
sops
];
programs.bash.shellAliases = {
cal = "${pkgs.calcurse}/bin/calcurse";
calendar = "${pkgs.calcurse}/bin/calcurse";
};
# Default modules for all users (machines can override with mkForce false)
modules.user = {
bash.enable = lib.mkDefault true;
git.enable = lib.mkDefault true;
neovim.enable = lib.mkDefault true;
security.gpg.enable = lib.mkDefault true;
};
}

Binary file not shown.

33
user/keys/default.nix Normal file
View file

@ -0,0 +1,33 @@
{ lib }:
with builtins;
let
extractName = filename:
let
# Remove .key extension
noKey = lib.removeSuffix ".key" filename;
# Remove .pub/.priv/.public/.private markers
noMarkers = replaceStrings
[ ".pub" ".priv" ".public" ".private" ]
[ "" "" "" "" ]
noKey;
in noMarkers;
constructKeys = dir: (
listToAttrs (
map (subdir: {
name = subdir;
value = listToAttrs (
map (file: {
name = extractName file;
value = readFile "${dir}/${subdir}/${file}";
}) (filter (file:
(readDir "${dir}/${subdir}").${file} == "regular" &&
lib.hasSuffix ".key" file
) (attrNames (readDir "${dir}/${subdir}")))
);
}) (filter (node: (readDir dir).${node} == "directory") (attrNames (readDir dir)))
)
);
in
constructKeys ./.

BIN
user/keys/pgp/company.pub.key Executable file

Binary file not shown.

BIN
user/keys/pgp/work.pub.key Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
user/keys/ssh/work.pub.key Normal file

Binary file not shown.

Binary file not shown.

@ -0,0 +1 @@
Subproject commit a90d89277c4bbd363d6929f434eef633bea439f5

View file

@ -0,0 +1,32 @@
{ lib, config, ... }:
with lib;
let
cfg = config.modules.user.bash;
in
{ options.modules.user.bash = { enable = mkEnableOption "user.bash"; };
config = mkIf cfg.enable {
programs.bash = {
enable = true;
initExtra = "source ~/.config/bash/bashrc";
};
home.file.".config/bash" = {
source = ./bash;
recursive = true;
};
programs = {
ripgrep.enable = true;
eza = {
enable = true;
enableBashIntegration = true;
enableFishIntegration = false;
enableZshIntegration = false;
enableNushellIntegration = false;
enableIonIntegration = false;
};
};
};
}

34
user/modules/default.nix Normal file
View file

@ -0,0 +1,34 @@
let
mkModules = dir: isRoot:
let
entries = builtins.readDir dir;
names = builtins.attrNames entries;
excludedDirs = [ "config" "scripts" ];
isSubmodule = path:
builtins.pathExists "${path}/.git" &&
builtins.readFileType "${path}/.git" == "regular";
isModuleDir = path:
builtins.pathExists path &&
builtins.readFileType path == "directory" &&
!(builtins.elem (builtins.baseNameOf path) excludedDirs) &&
!(isSubmodule path);
isModule = file: file == "default.nix";
in
builtins.concatMap (name:
let
path = "${dir}/${name}";
in
if isModuleDir path then
mkModules path false
else if isModule name && !isRoot then
[ dir ]
else
[]
) names;
in
{
imports = mkModules ./. true;
}

View file

@ -0,0 +1,32 @@
{ lib, pkgs, config, ... }:
with lib;
let
cfg = config.modules.user.git;
in
{ options.modules.user.git = { enable = mkEnableOption "user.git"; };
config = mkIf cfg.enable {
programs = {
git = {
enable = true;
};
gh = {
enable = true;
settings.git_protocol = "ssh";
};
};
home = {
packages = with pkgs; [
git-crypt
];
file.".config/git" = {
source = ./git;
recursive = true;
};
};
programs.bash.initExtra = import ./scripts/cdg.nix;
};
}

1
user/modules/git/git Submodule

@ -0,0 +1 @@
Subproject commit d394ee0594e8b1162f05547c3f7da817b6fcb62a

View file

@ -0,0 +1,24 @@
''
function cdg() {
if [[ $1 == "--help" ]]; then
echo "A simple utility for navigating to the root of a git repo"
return 0
fi
if [[ -n "$1" ]]; then
echo "Invalid command: $1. Try 'cdg --help'."
return 1
fi
local root_dir
root_dir=$(git rev-parse --show-toplevel 2>/dev/null)
local git_status=$?
if [ $git_status -ne 0 ]; then
echo "Error: Not a git repo."
return 1
fi
cd "$root_dir"
}
''

View file

@ -0,0 +1,83 @@
{ config, ... }:
let
hyprland = config.modules.user.gui.wm.hyprland;
in
{
scrolling = {
history = 10000;
multiplier = 3;
};
window = {
opacity = if hyprland.enable then 0.9 else 1;
};
keyboard.bindings = [
{
key = "Enter";
mods = "Alt | Shift";
action = "SpawnNewInstance";
}
];
colors = {
primary = {
background = "#000000";
foreground = "#cdd6f4";
};
normal = {
black = "#1e2127";
red = "#e06c75";
green = "#98c379";
yellow = "#d19a66";
blue = "#61afef";
magenta = "#c678dd";
cyan = "#56b6c2";
white = "#abb2bf";
};
bright = {
black = "#5c6370";
red = "#e06c75";
green = "#98c379";
yellow = "#d19a66";
blue = "#61afef";
magenta = "#c678dd";
cyan = "#56b6c2";
white = "#ffffff";
};
};
font = {
size = 12;
normal = {
family = "Terminess Nerd Font Propo";
style = "Regular";
};
bold = {
family = "Terminess Nerd Font Propo";
style = "Bold";
};
italic = {
family = "Terminess Nerd Font Propo";
style = "Italic";
};
bold_italic = {
family = "Terminess Nerd Font Propo";
style = "Bold Italic";
};
};
#cursor = {
# shape = "Block";
# blinking = "Always";
# blink_interval = 750;
#};
}

View file

@ -0,0 +1,15 @@
{ lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.alacritty;
in
{ options.modules.user.gui.alacritty = { enable = mkEnableOption "Enable Alacritty terminal"; };
config = mkIf cfg.enable {
programs.alacritty = {
enable = true;
settings = import ./config/alacritty.nix { inherit config; };
};
};
}

View file

@ -0,0 +1,53 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.browser.chromium;
in
{ options.modules.user.gui.browser.chromium = { enable = mkEnableOption "Enable Chromium browser"; };
config = mkIf cfg.enable {
programs = {
chromium = rec {
enable = true;
package = pkgs.ungoogled-chromium;
extensions =
let
vrs = package.version;
in
[
rec {
id = "cjpalhdlnbpafiamejdnhcphjbkeiagm";
crxPath = builtins.fetchurl {
url = "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=${vrs}&acceptformat=crx2,crx3&x=id%3D${id}%26uc";
name = "ublock_${version}.crx";
sha256 = "0ycnkna72n969crgxfy2lc1qbndjqrj46b9gr5l9b7pgfxi5q0ll";
};
version = "1.62.0";
}
rec {
id = "dbepggeogbaibhgnhhndojpepiihcmeb";
crxPath = builtins.fetchurl {
url = "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=${vrs}&acceptformat=crx2,crx3&x=id%3D${id}%26uc";
name = "vimium_${version}.crx";
sha256 = "0m8xski05w2r8igj675sxrlkzxlrl59j3a7m0r6c8pwcvka0r88d";
};
version = "2.1.2";
}
rec {
id = "naepdomgkenhinolocfifgehidddafch";
crxPath = builtins.fetchurl {
url = "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=${vrs}&acceptformat=crx2,crx3&x=id%3D${id}%26uc";
name = "browserpass_${version}.crx";
sha256 = "074sc9hxh7vh5j79yjhsrnhb5k4dv3bh5vip0jr30hkkni7nygbd";
};
version = "3.9.0";
}
];
};
browserpass = {
enable = true;
};
};
};
}

View file

@ -0,0 +1,338 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.browser.firefox;
passffHost= {
home.packages = with pkgs; [
passff-host
];
home.file = {
".mozilla/native-messaging-hosts/passff.json" = {
text = ''
{
"name": "passff",
"description": "Host for communicating with zx2c4 pass",
"path": "${pkgs.passff-host}/share/passff-host/passff.py",
"type": "stdio",
"allowed_extensions": [ "passff@invicem.pro" ]
}
'';
};
};
assertions =
let
pinentry = config.services.gpg-agent.pinentry.package;
in
[
{
assertion = pinentry != pkgs.pinentry-curses || pinentry != pkgs.pinentry-tty;
message = "Firefox plugin passff requires graphical pinentry";
}
];
};
in
{
options.modules.user.gui.browser.firefox = { enable = mkEnableOption "Enable Firefox browser"; };
config = mkIf cfg.enable (passffHost // {
programs.firefox = {
enable = true;
profiles = {
"${config.user.name}" = {
isDefault = true;
#bookmarks = config.user.bookmarks;
extensions = {
packages = with pkgs.nur.repos.rycee.firefox-addons; [
ublock-origin
tridactyl
#darkreader
tampermonkey
clearurls
passff
multi-account-containers
];
};
search = {
force = true;
default = "google";
engines = {
"Startpage" = {
urls = [{
template = "https://www.startpage.com/sp/search?q={searchTerms}";
}];
icon = "https://www.startpage.com/sp/cdn/favicons/favicon--default.ico";
};
};
};
containersForce = true;
containers = {
Banking = {
color = "green";
icon = "dollar";
id = 1;
};
Personal = {
color = "orange";
icon = "fingerprint";
id = 2;
};
Work = {
color = "yellow";
icon = "briefcase";
id = 3;
};
Personal_Work = {
color = "turquoise";
icon = "briefcase";
id = 4;
};
Social = {
color = "red";
icon = "chill";
id = 5;
};
Shopping = {
color = "purple";
icon = "cart";
id = 6;
};
Google = {
color = "pink";
icon = "vacation";
id = 7;
};
};
settings = {
"layout.spellcheckDefault" = 0;
"ui.key.menuAccessKeyFocuses" = false;
"signon.rememberSignons" = false;
"extensions.pocket.enabled" = false;
"extensions.autoDisableScopes" = 0;
# May break extensions due to Nix
"extensions.enabledScopes" = 5;
# May break stuff but increases privacy
#"extensions.webextensions.restrictedDomains" = "";
#"privacy.resistFingerprinting" = true;
#"privacy.resistFingerprinting.letterboxing" = true;
#"privacy.resistFingerprinting.block_mozAddonManager" = true;
"browser.startup.homepage_override.mstone" = "ignore";
"browser.aboutConfig.showWarning" = false;
"browser.startup.page" = 0;
"browser.formfill.enable" = false;
"places.history.enabled" = false;
"browser.urlbar.suggest.history" = false;
"browser.urlbar.suggest.topsites" = false;
"browser.urlbar.sponsoredTopSites" = false;
"browser.urlbar.autoFill" = false;
"browser.urlbar.suggest.pocket" = false;
"browser.urlbar.suggest.quicksuggest.nonsponsored" = false;
"browser.urlbar.suggest.quicksuggest.sponsored" = false;
"browser.toolbars.bookmarks.showOtherBookmarks" = false;
"browser.aboutwelcome.showModal" = false;
"browser.migrate.content-modal.about-welcome-behavior" = "";
"browser.newtabpage.enabled" = false;
"browser.newtabpage.activity-stream.showSponsored" = false;
"browser.newtabpage.activity-stream.showSponsoredTopSites" = false;
"browser.newtabpage.activity-stream.default.sites" = "";
"extensions.getAddons.showPane" = false;
"extensions.htmlaboutaddons.recommendations.enabled" = false;
"browser.discovery.enabled" = false;
"browser.shopping.experience2023.enabled" = false;
"datareporting.policy.dataSubmissionEnabled" = false;
"datareporting.healthreport.uploadEnabled" = false;
"toolkit.telemetry.unified" = false;
"toolkit.telemetry.enabled" = false;
"toolkit.telemetry.server" = "";
"toolkit.telemetry.archive.enabled" = false;
"toolkit.telemetry.newProfilePing.enabled" = false;
"toolkit.telemetry.shutdownPingSender.enabled" = false;
"toolkit.telemetry.updatePing.enabled" = false;
"toolkit.telemetry.bhrPing.enabled" = false;
"toolkit.telemetry.firstShutdownPing.enabled" = false;
"toolkit.telemetry.coverage.opt-out" = false;
"toolkit.coverage.endpoint.base" = "";
"browser.newtabpage.activity-stream.feeds.telemetry" = false;
"browser.newtabpage.activity-stream.telemetry" = false;
"app.shield.optoutstudies.enabled" = false;
"app.normandy.enabled" = false;
"app.normandy.api_url" = "";
"breakpad.reportURL" = false;
"browser.tabs.crashReporting.sendReport" = false;
"browser.crashReports.unsubmittedCheck.autoSubmit2" = false;
"captivedetect.canonicalURL" = "";
"network.captive-portal-service.enabled" = false;
"network.connectivity-service.enabled" = false;
"browser.safebrowsing.downloads.remote.enabled" = false;
"network.prefetch-next" = false;
"network.dns.disablePrefetch" = true;
"network.predictor.enabled" = false;
"network.predictor.enable-prefetch" = false;
"network.http.speculative-parallel-limit" = 0;
"network.http.speculativeConnect.enabled" = false;
"network.proxy.sock_remote_dns" = true;
"network.file.disable_unc_paths" = true;
"network.gio.supported-protocols" = "";
"browser.urlbar.speculativeConnect.enabled" = false;
"browser.search.suggest.enabled" = false;
"browser.urlbar.suggest.searches" = false;
"browser.urlbar.clipboard.featureGate" = false;
"browser.urlbar.richSuggestions.featureGate" = false;
"browser.urlbar.trending.featureGate" = false;
"browser.urlbar.addons.featureGate" = false;
"browser.urlbar.pocket.featureGate" = false;
"browser.urlbar.weather.featureGate" = false;
"browser.urlbar.yelp.featureGate" = false;
"browser.urlbar.suggest.engines" = false;
"signon.autofillForms" = false;
"signon.formlessCapture.enabled" = false;
"network.auth.subresource-http-auth-allow" = 1;
"browser.privatebrowsing.forceMediaMemoryCache" = true;
"media.memory_cache_max_size" = 65536;
"browser.sessionstore.privacy_level" = 2;
"browser.shell.shortcutFavicons" = false;
"security.ssl.require_safe_negotiation" = true;
"security.tls.enable_0rtt_data" = false;
"security.OCSP.enabled" = true;
"security.OCSP.require" = true;
"security.cert_pinning.enforcement_level" = 2;
"security.remote_settings.crlite_filters.enabled" = true;
"security.pki.crlite_mode" = 2;
"dom.security.https_only_mode" = true;
"dom.security.https_only_mode_send_http_background_request" = false;
"security.ssl.treat_unsafe_negotiation_as_broken" = true;
"browser.xul.error_pages.expert_bad_cert" = true;
"network.http.referer.XOriginTrimmingPolicy" = 2;
"privacy.userContext.enabled" = true;
"privacy.userContext.ui.enabled" = true;
"media.peerconnection.ice.proxy_only_if_behind_proxy" = true;
"media.peerconnection.ice.default_address_only" = true;
"dom.disable_window_move_resize" = true;
"browser.download.start_downloads_in_tmp_dir" = false;
"browser.helperApps.deleteTempFileOnExit" = true;
"browser.uitour.enabled" = false;
"devtools.debugger.remote-enabled" = false;
"permissions.manager.defaultsUrl" = "";
"webchannel.allowObject.urlWhitelist" = "";
"network.IDN_show_punycode" = true;
"pdfjs.disabled" = false;
"pdfjs.enableScripting" = false;
"browser.tabs.searchclipboardfor.middleclick" = false;
"browser.contentanalysis.default_allow" = false;
"browser.download.useDownloadDir" = true;
"browser.download.alwaysOpenPanel" = true;
"browser.download.manager.addToRecentDocs" = false;
"browser.download.always_ask_before_handling_new_types" = true;
"extensions.postDownloadThirdPartyPrompt" = true;
"browser.contentblocking.category" = "strict";
"privacy.sanitize.sanitizeOnShutdown" = true;
"privacy.clearOnShutdown.cache" = true;
"privacy.clearOnShutdown_v2.cache" = true;
"privacy.clearOnShutdown.downloads" = true;
"privacy.clearOnShutdown.formdata" = true;
"privacy.clearOnShutdown.history" = true;
"privacy.clearOnShutdown_v2.historyFormDataAndDownloads" = true;
"privacy.clearOnShutdown.cookies" = false;
"privacy.clearOnShutdown.offlineApss" = true;
"privacy.clearOnShutdown.sessions" = true;
"privacy.clearOnShutdown_v2.cookiesAndStorage" = false;
"privacy.clearSiteData.cache" = true;
"privacy.clearSiteData.cookiesAndStorage" = false;
"privacy.clearSiteData.historyFormDataAndDownloads" = true;
"privacy.cpd.cache" = true;
"privacy.clearHistory.cache" = true;
"privacy.cpd.formdata" = true;
"privacy.cpd.history" = true;
"privacy.clearHistory.historyFormDataAndDownloads" = true;
"privacy.cpd.cookies" = false;
"privacy.cpd.sessions" = true;
"privacy.cpd.offlineApps" = false;
"privacy.clearHistory.cookiesAndStorage" = false;
"privacy.sanitize.timeSpan" = 0;
"privacy.window.maxInnerWidth" = 1600;
"privacy.window.maxInnerHeight" = 900;
"privacy.spoof_english" = 1;
"browser.display.use_system_colors" = false;
"widget.non-native-theme.enabled" = true;
"browser.link.open_newwindow" = 3;
"browser.link.open_newwindow.restriction" = 0;
"browser.chrome.site_icons" = false;
"browser.download.forbid_open_with" = true;
"extensions.blocklist.enabled" = true;
"network.http.referer.spoofSource" = false;
"security.dialog_enable_delay" = 1000;
"privacy.firstparty.isolate" = false;
"extensions.webcompat.enable_shims" = true;
"security.tls.version.enable-deprecated" = false;
"extensions.webcompat-reporter.enabled" = false;
"extensions.quarantinedDomains.enabled" = true;
"media.videocontrols.picture-in-picture.enabled" = false;
# VA-API hardware video acceleration (NVIDIA)
"media.ffmpeg.vaapi.enabled" = true;
"media.rdd-ffmpeg.enabled" = true;
"media.av1.enabled" = false; # GTX 1650 doesn't support AV1 decode
"gfx.x11-egl.force-enabled" = true;
};
};
};
policies = {
WebsiteFilter = {
Block = [
"*://*.pokemonshowdown.com/*"
];
};
};
};
});
}

View file

@ -0,0 +1,36 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.corn;
in
{ options.modules.user.gui.corn = { enable = mkEnableOption "Enable Bitcoin client applications"; };
config = mkIf cfg.enable {
home.packages = with pkgs; [
#trezor-suite
#trezorctl
#trezord
sparrow
];
#systemd.user.services = {
# trezord = {
# Unit = {
# Description = "Trezor Bridge";
# After = [ "network.target" ];
# Wants = [ "network.target" ];
# PartOf = [ "graphical-session.target" ];
# };
# Service = {
# ExecStart = "${pkgs.trezord}/bin/trezord-go";
# Restart = "always";
# };
# Install = {
# WantedBy = [ "default.target" ];
# };
# };
#};
};
}

View file

@ -0,0 +1,30 @@
{ lib, config, ... }:
let
programs = config.programs;
defaultBrowser =
if programs.firefox.enable then "firefox.desktop"
else if programs.brave.enable then "brave-browser.desktop"
else if programs.chromium.enable then "chromium.desktop"
else null;
types = [
"text/html" "application/xhtml+xml"
"x-scheme-handler/http" "x-scheme-handler/https"
"application/pdf"
"image/png" "image/jpeg" "image/jpg" "image/gif"
"image/webp" "image/avif" "image/bmp" "image/tiff" "image/svg+xml"
"video/mp4" "video/webm" "video/mkv" "video/avi"
"video/x-matroska" "video/quicktime"
];
in
{
xdg.mimeApps = lib.mkIf (defaultBrowser != null) {
enable = true;
defaultApplications = builtins.listToAttrs (
map (t: { name = t; value = [ defaultBrowser ]; }) types
);
};
}

View file

@ -0,0 +1,14 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.dev.design;
in
{ options.modules.user.gui.dev.design = { enable = mkEnableOption "Enable design tools"; };
config = mkIf cfg.enable {
home.packages = with pkgs; [
penpot-desktop
];
};
}

View file

@ -0,0 +1,16 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.dev.pcb;
in
{ options.modules.user.gui.dev.pcb = { enable = mkEnableOption "Enable PCB development suite"; };
config = mkIf cfg.enable {
home.packages = with pkgs; [
arduino-ide
kicad-small
ngspice
];
};
}

View file

@ -0,0 +1,82 @@
{
"settings": {
"general": {
"menuBar": {
"hide": false
},
"tray": {
"disable": false
},
"taskbar": {
"flash": true
},
"window": {
"transparent": false,
"hideOnClose": false
}
},
"privacy": {
"blockApi": {
"science": true,
"typingIndicator": false,
"fingerprinting": true
},
"permissions": {
"video": null,
"audio": true,
"fullscreen": true,
"notifications": null,
"display-capture": true,
"background-sync": false
}
},
"advanced": {
"csp": {
"enabled": true
},
"cspThirdParty": {
"spotify": true,
"gif": true,
"hcaptcha": true,
"youtube": true,
"twitter": true,
"twitch": true,
"streamable": true,
"vimeo": true,
"soundcloud": true,
"paypal": true,
"audius": true,
"algolia": true,
"reddit": true,
"googleStorageApi": true
},
"currentInstance": {
"radio": 0
},
"devel": {
"enabled": true
},
"redirection": {
"warn": true
},
"optimize": {
"gpu": true
},
"webApi": {
"webGl": true
},
"unix": {
"autoscroll": false
}
}
},
"update": {
"notification": {
"version": "",
"till": ""
}
},
"screenShareStore": {
"audio": false
}
}

View file

@ -0,0 +1,26 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.fun;
in
{ options.modules.user.gui.fun = { enable = mkEnableOption "Enable entertainment apps"; };
config = mkIf cfg.enable {
#programs.obs-studio = {
# enable = true;
# plugins = with pkgs.obs-studio-plugins; [
# wlrobs
# obs-pipewire-audio-capture
# input-overlay
# ];
#};
home.packages = with pkgs; [
ytmdesktop
#discordo
#webcord
discord
];
};
}

View file

@ -0,0 +1,16 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.gui.utils;
in
{ options.modules.user.gui.utils = { enable = mkEnableOption "Enable desktop utils"; };
config = mkIf cfg.enable {
programs.btop.enable = true;
home.packages = with pkgs; [
gimp
libreoffice
];
};
}

View file

@ -0,0 +1,7 @@
configuration {
font: "SF Pro Rounded 10";
show-icons: true;
kb-cancel: "Escape,Alt+F1";
}
@theme "~/.config/rofi/material-ocean.rasi"

View file

@ -0,0 +1,95 @@
* {
background: #0f111a;
foreground: #f1f1f1;
selected: #ff4151;
}
window {
transparency: "real";
background-color: @background;
text-color: @foreground;
}
prompt {
enabled: true;
padding: 4px 4px 6px 6px;
background-color: @background;
text-color: @foreground;
}
textbox-prompt-colon {
expand: false;
background-color: @background;
padding: 4px 0px 0px 6px;
}
inputbar {
children: [ textbox-prompt-colon, entry ];
background-color: @background;
text-color: @foreground;
expand: false;
border: 0px 0px 0px 0px;
border-radius: 0px;
border-color: @selected;
margin: 0px 0px 0px 0px;
padding: 0px 0px 4px 0px;
position: center;
}
entry {
background-color: @background;
text-color: @foreground;
placeholder-color: @foreground;
expand: true;
horizontal-align: 0;
blink: true;
padding: 4px 0px 0px 4px;
}
case-indicator {
background-color: @background;
text-color: @foreground;
spacing: 0;
}
listview {
background-color: @background;
columns: 1;
spacing: 5px;
cycle: true;
dynamic: true;
layout: vertical;
}
mainbox {
background-color: @background;
children: [ inputbar, listview ];
spacing: 5px;
padding: 5px 5px 5px 5px;
}
element {
background-color: @background;
text-color: @foreground;
orientation: horizontal;
border-radius: 4px;
padding: 6px 6px 6px 6px;
}
element-text, element-icon {
background-color: inherit;
text-color: inherit;
}
element-icon {
size: 18px;
border: 4px;
}
element selected {
background-color: @selected;
text-color: @background;
border: 0px;
border-radius: 0px;
border-color: @selected;
}

View file

@ -0,0 +1,183 @@
{ pkgs, config, ... }:
let
inherit (config.lib.formats.rasi) mkLiteral;
in
{
enable = true;
package = pkgs.rofi;
location = "center";
terminal = "\${pkgs.alacritty}/bin/alacritty";
plugins = with pkgs; [
rofi-emoji
];
#theme = {
# "*" = {
# nord0 = mkLiteral "#2e3440";
# nord1 = mkLiteral "#3b4252";
# nord2 = mkLiteral "#434c5e";
# nord3 = mkLiteral "#4c566a";
# nord4 = mkLiteral "#d8dee9";
# nord5 = mkLiteral "#e5e9f0";
# nord6 = mkLiteral "#eceff4";
# nord7 = mkLiteral "#8fbcbb";
# nord8 = mkLiteral "#88c0d0";
# nord9 = mkLiteral "#81a1c1";
# nord10 = mkLiteral "#5e81ac";
# nord11 = mkLiteral "#bf616a";
# nord12 = mkLiteral "#d08770";
# nord13 = mkLiteral "#ebcb8b";
# nord14 = mkLiteral "#a3be8c";
# nord15 = mkLiteral "#b48ead";
# spacing = 2;
# background-color = mkLiteral "var(nord1)";
# background = mkLiteral "var(nord1)";
# foreground = mkLiteral "var(nord4)";
# normal-background = mkLiteral "var(background)";
# normal-foreground = mkLiteral "var(foreground)";
# alternate-normal-background = mkLiteral "var(background)";
# alternate-normal-foreground = mkLiteral "var(foreground)";
# selected-normal-background = mkLiteral "var(nord8)";
# selected-normal-foreground = mkLiteral "var(background)";
# active-background = mkLiteral "var(background)";
# active-foreground = mkLiteral "var(nord10)";
# alternate-active-background = mkLiteral "var(background)";
# alternate-active-foreground = mkLiteral "var(nord10)";
# selected-active-background = mkLiteral "var(nord10)";
# selected-active-foreground = mkLiteral "var(background)";
# urgent-background = mkLiteral "var(background)";
# urgent-foreground = mkLiteral "var(nord11)";
# alternate-urgent-background = mkLiteral "var(background)";
# alternate-urgent-foreground = mkLiteral "var(nord11)";
# selected-urgent-background = mkLiteral "var(nord11)";
# selected-urgent-foreground = mkLiteral "var(background)";
# };
#
# element = {
# padding = mkLiteral "0px 0px 0px 7px";
# spacing = mkLiteral "5px";
# border = 0;
# cursor = mkLiteral "pointer";
# };
# "element normal.normal" = {
# background-color = mkLiteral "var(normal-background)";
# text-color = mkLiteral "var(normal-foreground)";
# };
# "element normal.urgent" = {
# background-color = mkLiteral "var(urgent-background)";
# text-color = mkLiteral "var(urgent-foreground)";
# };
# "element normal.active" = {
# background-color = mkLiteral "var(active-background)";
# text-color = mkLiteral "var(active-foreground)";
# };
# "element selected.normal" = {
# background-color = mkLiteral "var(selected-normal-background)";
# text-color = mkLiteral "var(selected-normal-foreground)";
# };
# "element selected.urgent" = {
# background-color = mkLiteral "var(selected-urgent-background)";
# text-color = mkLiteral "var(selected-urgent-foreground)";
# };
# "element selected.active" = {
# background-color = mkLiteral "var(selected-active-background)";
# text-color = mkLiteral "var(selected-active-foreground)";
# };
# "element alternate.normal" = {
# background-color = mkLiteral "var(alternate-normal-background)";
# text-color = mkLiteral "var(alternate-normal-foreground)";
# };
# "element alternate.urgent" = {
# background-color = mkLiteral "var(alternate-urgent-background)";
# text-color = mkLiteral "var(alternate-urgent-foreground)";
# };
# "element alternate.active" = {
# background-color = mkLiteral "var(alternate-active-background)";
# text-color = mkLiteral "var(alternate-active-foreground)";
# };
# "element-text" = {
# background-color = mkLiteral "rgba(0, 0, 0, 0%)";
# text-color = mkLiteral "inherit";
# highlight = mkLiteral "inherit";
# cursor = mkLiteral "inherit";
# };
# "element-icon" = {
# background-color = mkLiteral "rgba(0, 0, 0, 0%)";
# size = mkLiteral "1.0000em";
# text-color = mkLiteral "inherit";
# cursor = mkLiteral "inherit";
# };
# window = {
# padding = 0;
# border = 0;
# background-color = mkLiteral "var(background)";
# };
# mainbox = {
# padding = 0;
# border = 0;
# };
# message = {
# margin = mkLiteral "0px 7px";
# };
# textbox = {
# text-color = mkLiteral "var(foreground)";
# };
# listview = {
# margin = mkLiteral "0px 0px 5px";
# scrollbar = true;
# spacing = mkLiteral "2px";
# fixed-height = 0;
# };
# scrollbar = {
# padding = 0;
# handle-width = mkLiteral "14px";
# border = 0;
# handle-color = mkLiteral "var(nord3)";
# };
# button = {
# spacing = 0;
# text-color = mkLiteral "var(normal-foreground)";
# cursor = mkLiteral "pointer";
# };
# "button selected" = {
# background-color = mkLiteral "var(selected-normal-background)";
# text-color = mkLiteral "var(selected-normal-foreground)";
# };
# inputbar = {
# padding = mkLiteral "7px";
# margin = mkLiteral "7px";
# spacing = 0;
# text-color = mkLiteral "var(normal-foreground)";
# background-color = mkLiteral "var(nord3)";
# children = [ "entry" ];
# };
# entry = {
# spacing = 0;
# cursor = mkLiteral "text";
# text-color = mkLiteral "var(normal-foreground)";
# background-color = mkLiteral "var(nord3)";
# };
#};
}

View file

@ -0,0 +1,126 @@
{
"layer": "top",
"position": "top",
"output": "HDMI-A-1",
"modules-left": [ "custom/logo", "clock", "custom/blockheight", "custom/price", "memory", "cpu" ],
"modules-center": [ "hyprland/workspaces" ],
"modules-right": [ "tray", "pulseaudio", "network" ],
"reload_style_on_change":true,
"custom/logo": {
"format": "",
"tooltip": false,
"on-click": "alacritty --class sys-specs -e bash -c 'fastfetch; read -n 1'"
},
"hyprland/workspaces": {
"format": "<span color='#ffffff'>{icon}</span>",
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": "",
"active": "",
"default": ""
},
"persistent-workspaces": {
"*": [ 2, 3, 4, 5, 6 ]
}
},
"custom/weather": {
"format": "<span color='#ffffff'>{}</span>",
"return-type": "json",
"exec": "~/.config/waybar/scripts/weather.sh",
"interval": 10,
},
"custom/blockheight": {
"format": "<span color='#ef8e19'>󰠓 </span><span color='#ffffff'>{} </span>",
"interval": 30,
"exec": "~/.config/waybar/scripts/getBlock",
"on-click": "xdg-open https://www.mempool.space",
},
"custom/price": {
"format": "<span color='#00ff00'>$</span><span color='#ffffff'>{}</span>",
"interval": 10,
"exec": "~/.config/waybar/scripts/getPrice",
"on-click": "xdg-open https://www.coinbase.com/price/bitcoin",
},
"clock": {
"format": "{:%I:%M:%S %p}",
"interval":1,
"tooltip-format": "\n<big>{:%d %A}</big>\n<tt><small>{calendar}</small></tt>",
"calendar-weeks-pos": "right",
"today-format": "<span color='#7645AD'><b><u>{}</u></b></span>",
"format-calendar": "<span color='#aeaeae'><b>{}</b></span>",
"format-calendar-weeks": "<span color='#aeaeae'><b>W{:%V}</b></span>",
"format-calendar-weekdays": "<span color='#aeaeae'><b>{}</b></span>"
},
"network": {
"format-wifi": "<span color='#ffffff'> </span>",
"format-ethernet":"<span color='#ffffff'>󰌘</span>",
"format-disconnected": "<span class='#e11e2d'></span>",
"tooltip-format": "{ipaddr}",
"tooltip-format-wifi": "{essid} ({signalStrength}%) | {ipaddr}",
"tooltip-format-ethernet": "{ifname} | {ipaddr}",
"tooltip-format-disconnected": "Offline",
"on-click": "alacritty -e nmtui"
},
"cpu": {
"interval": 1,
"format": "<span color='#ffd808'> </span><span color='#ffffff'> {usage}%</span>",
"min-length": 6,
"max-length": 6,
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
},
"memory": {
"format": "<span color='#cc63ff'>󱐋</span><span color='#ffffff'>{percentage}%</span>"
},
"temperature": {
"format": "<span color='#99c3ff'>󱩱 </span><span color='#ffffff'>:{temperatureC}°C</span>",
"format-critical": "<span color='#fd4400'>󰈸</span><span color='#ffffff'>:{temperatureC}°C</span>",
"interval": 1,
"critical-threshold": 80,
"on-click": "alacritty -e btop",
},
"pulseaudio": {
"format": "<span color='#ffffff'>{icon}</span>",
"format-bluetooth":"󰂰",
"format-muted": "<span font='12'></span>",
"format-icons": {
"headphones": "",
"bluetooth": "󰥰",
"handsfree": "",
"headset": "󱡬",
"phone": "",
"portable": "",
"car": "",
"default": ["","",""]
},
"justify": "center",
"on-click": "alacritty -e pulsemixer",
"tooltip-format": "{volume}%"
},
"jack": {
"format": "{} 󱎔",
"format-xrun": "{xruns} xruns",
"format-disconnected": "DSP off",
"realtime": true
},
"tray": {
"icon-size": 14,
"spacing": 10
},
}

Binary file not shown.

View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
response=$(curl -s "https://api.coinbase.com/v2/prices/BTC-USD/spot")
price=$(echo "$response" | jq -r .data.amount | awk -F. '{print $1}')
if [ -z "$price" ]; then
echo -e "\033[31mErr\033[0m"
exit 1
fi
echo "$price"
exit 0

View file

@ -0,0 +1,24 @@
#!/bin/sh
BSSIDS="$(nmcli device wifi list |
awk 'NR>1 {if ($1 != "*") {print $1}}' |
tr -d ":" |
tr "\n" ",")"
LOC=""
REQUEST_GEO="$(wget -qO - http://openwifi.su/api/v1/bssids/"$BSSIDS")"
if [[ "$(jq ".count_results" <<< "$REQUEST_GEO")" -gt 0 ]] ; then
LAT="$(jq ".lat" <<< "$REQUEST_GEO")"
LON="$(jq ".lon" <<< "$REQUEST_GEO")"
LOC="$LAT,$LON"
fi
text="$(curl -s "https://wttr.in/$LOC?format=1" | sed 's/ //g')"
tooltip="$(curl -s "https://wttr.in/$LOC?0QT" |
sed 's/\\/\\\\/g' |
sed ':a;N;$!ba;s/\n/\\n/g' |
sed 's/"/\\"/g')"
if ! grep -q "Unknown location" <<< "$text"; then
echo "{\"text\": \"$text\", \"tooltip\": \"<tt>$tooltip</tt>\", \"class\": \"weather\"}"
fi

View file

@ -0,0 +1,73 @@
* {
border: none;
font-size: 14px;
font-family: "Terminus Nerd Font Propo" ;
min-height: 25px;
}
window#waybar {
background: transparent;
margin: 5px;
}
#custom-logo {
padding: 0 10px;
color: #5277c3;
}
.modules-right {
padding-left: 5px;
border-radius: 15px 0 0 15px;
margin-top: 2px;
background: #000000;
}
.modules-center {
padding: 0 15px;
margin-top: 2px;
border-radius: 15px 15px 15px 15px;
background: #000000;
}
.modules-left {
border-radius: 0 15px 15px 0;
margin-top: 2px;
background: #000000;
}
#custom-clipboard,
#pulseaudio,
#network,
#disk,
#memory,
#backlight,
#cpu,
#temperature,
#custom-weather,
#jack,
#tray,
#window,
#workspaces,
#clock {
padding: 0 5px;
}
#pulseaudio {
padding-top: 3px;
}
#temperature.critical,
#pulseaudio.muted {
color: #FF0000;
padding-top: 0;
}
#clock{
color: #5fd1fa;
}
@keyframes blink {
to {
background-color: #ffffff;
color: #000000;
}
}

View file

@ -0,0 +1,246 @@
{ pkgs, lib, config, monitors ? [], ... }:
with lib;
let
cfg = config.modules.user.gui.wm.hyprland;
wallpaper = builtins.fetchurl {
url = "https://images6.alphacoders.com/117/1174033.png";
sha256 = "1ph5m9s57076jx6042iipqx2ifzadmd5z4lf5l49wgq4jb92mp16";
};
toHyprlandMonitor = m:
"${m.name}, ${toString m.width}x${toString m.height}@${toString m.refreshRate}, ${toString m.x}x${toString m.y}, ${toString m.scale}";
in
{ options.modules.user.gui.wm.hyprland = { enable = mkEnableOption "Enable Hyprland WM"; };
config = mkIf cfg.enable {
wayland.windowManager.hyprland = {
enable = true;
xwayland.enable = true;
settings = {
"$mod" = "ALT";
"$terminal" = "${pkgs.alacritty}/bin/alacritty";
"$menu" = "rofi -show drun -show-icons -drun-icon-theme Qogir -font 'Noto Sans 14'";
monitor = if monitors != []
then map toHyprlandMonitor monitors
else [ ", preferred, auto, 1" ];
exec-once = [
"waybar"
"hyprctl setcursor Vanilla-DMZ 24"
];
bind = [
"$mod, Return, exec, $terminal"
"$mod, q, killactive"
"$mod, J, movefocus, d"
"$mod, K, movefocus, u"
"$mod, H, movefocus, l"
"$mod, L, movefocus, r"
"$mod&SHIFT, J, movewindow, d"
"$mod&SHIFT, K, movewindow, u"
"$mod&SHIFT, H, movewindow, l"
"$mod&SHIFT, L, movewindow, r"
"$mod, F, fullscreen"
", Print, exec, grim ~/Pictures/screenshot-$(date +'%Y%m%d-%H%M%S').png"
"$mod&SHIFT, Print, exec, grim -g \"$(slurp)\" ~/Pictures/screenshot-$(date +'%Y%m%d-%H%M%S').png"
"$mod&SHIFT, F, exec, alacritty -e sh -c 'EDITOR=nvim ranger'"
''SHIFT, Print, exec, grim -g "$(hyprctl activewindow -j | jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"')" ~/Pictures/screenshot-$(date +'%Y%m%d-%H%M%S').png''
"$mod, D, exec, $menu"
"$mod&SHIFT, D, exec, rofi -modi emoji -show emoji"
] ++ ( builtins.concatLists (builtins.genList (
x: let
ws = let
c = (x + 1) / 10;
in
builtins.toString (x + 1 - (c * 10));
in
[
"$mod, ${ws}, workspace, ${toString (x + 1)}"
"$mod SHIFT, ${ws}, movetoworkspace, ${toString (x + 1)}"
])
10)
);
bindm = [
"$mod, mouse:272, movewindow"
];
windowrulev2 = [
"float, title:(Android Emulator)"
"float, title: Extension: (PassFF)"
"float, size 400 600, stayfocused, class:sys-specs"
];
general = {
layout = "master";
border_size = 0;
};
decoration = {
rounding = 10;
};
master = {
drop_at_cursor = false;
#new_is_master = false;
};
input = {
kb_layout = "us";
follow_mouse = 1;
accel_profile = "flat";
sensitivity = 0.35;
};
cursor = {
inactive_timeout = 0;
no_hardware_cursors = true;
hide_on_touch = false;
use_cpu_buffer = 0;
enable_hyprcursor = false;
};
env = [
"HYPRCURSOR_THEME,Vanilla-DMZ"
"HYPRCURSOR_SIZE,24"
"GTK_THEME,Juno"
"LIBVA_DRIVER_NAME,nvidia"
"XDG_SESSION_TYPE,wayland"
"GBM_BACKEND,nvidia-drm"
"__GLX_VENDOR_LIBRARY_NAME,nvidia"
];
};
};
programs.rofi = {
enable = true;
package = pkgs.rofi;
location = "center";
terminal = "alacritty";
plugins = with pkgs; [
rofi-emoji
];
};
home = {
file = {
".config/rofi" = {
source = ./config/rofi/config;
recursive = true;
};
".config/waybar" = {
source = ./config/waybar;
recursive = true;
};
};
packages = with pkgs; [
pulsemixer
xdg-utils
wl-clipboard
cliphist
dconf
grim
jq
slurp
ranger
highlight
noto-fonts
noto-fonts-cjk-sans
noto-fonts-color-emoji
];
sessionVariables = {
NIXOS_OZONE_WL = 1;
};
};
programs.waybar = {
enable = true;
};
services.hyprpaper = {
enable = true;
settings = {
ipc = "on";
splash = false;
splash_offset = 2.0;
preload =
[ "${wallpaper}" ];
wallpaper = [
",${wallpaper}"
];
};
};
gtk = {
enable = true;
theme = {
name = "Juno";
package = pkgs.juno-theme;
};
iconTheme = {
name = "Qogir";
package = pkgs.qogir-icon-theme;
};
cursorTheme = {
package = pkgs.vanilla-dmz;
name = "Vanilla-DMZ";
};
gtk3.extraConfig = {
gtk-application-prefer-dark-theme = 1;
};
gtk4.extraConfig = {
gtk-application-prefer-dark-theme = 1;
};
};
qt = {
enable = true;
style = {
name = "juno";
package = pkgs.juno-theme;
};
platformTheme.name = "gtk";
};
xdg.portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-hyprland
];
config.common.default = "*";
};
programs = {
imv.enable = true;
mpv.enable = true;
zathura.enable = true;
};
fonts.fontconfig.enable = true;
# Auto-start Hyprland on tty1
programs.bash.profileExtra = ''
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
exec Hyprland
fi
'';
};
}

View file

@ -0,0 +1,7 @@
configuration {
font: "SF Pro Rounded 10";
show-icons: true;
kb-cancel: "Escape,Alt+F1";
}
@theme "~/.config/rofi/material-ocean.rasi"

View file

@ -0,0 +1,95 @@
* {
background: #0f111a;
foreground: #f1f1f1;
selected: #ff4151;
}
window {
transparency: "real";
background-color: @background;
text-color: @foreground;
}
prompt {
enabled: true;
padding: 4px 4px 6px 6px;
background-color: @background;
text-color: @foreground;
}
textbox-prompt-colon {
expand: false;
background-color: @background;
padding: 4px 0px 0px 6px;
}
inputbar {
children: [ textbox-prompt-colon, entry ];
background-color: @background;
text-color: @foreground;
expand: false;
border: 0px 0px 0px 0px;
border-radius: 0px;
border-color: @selected;
margin: 0px 0px 0px 0px;
padding: 0px 0px 4px 0px;
position: center;
}
entry {
background-color: @background;
text-color: @foreground;
placeholder-color: @foreground;
expand: true;
horizontal-align: 0;
blink: true;
padding: 4px 0px 0px 4px;
}
case-indicator {
background-color: @background;
text-color: @foreground;
spacing: 0;
}
listview {
background-color: @background;
columns: 1;
spacing: 5px;
cycle: true;
dynamic: true;
layout: vertical;
}
mainbox {
background-color: @background;
children: [ inputbar, listview ];
spacing: 5px;
padding: 5px 5px 5px 5px;
}
element {
background-color: @background;
text-color: @foreground;
orientation: horizontal;
border-radius: 4px;
padding: 6px 6px 6px 6px;
}
element-text, element-icon {
background-color: inherit;
text-color: inherit;
}
element-icon {
size: 18px;
border: 4px;
}
element selected {
background-color: @selected;
text-color: @background;
border: 0px;
border-radius: 0px;
border-color: @selected;
}

View file

@ -0,0 +1,183 @@
{ pkgs, config, ... }:
let
inherit (config.lib.formats.rasi) mkLiteral;
in
{
enable = true;
package = pkgs.rofi;
location = "center";
terminal = "\${pkgs.alacritty}/bin/alacritty";
plugins = with pkgs; [
rofi-emoji
];
#theme = {
# "*" = {
# nord0 = mkLiteral "#2e3440";
# nord1 = mkLiteral "#3b4252";
# nord2 = mkLiteral "#434c5e";
# nord3 = mkLiteral "#4c566a";
# nord4 = mkLiteral "#d8dee9";
# nord5 = mkLiteral "#e5e9f0";
# nord6 = mkLiteral "#eceff4";
# nord7 = mkLiteral "#8fbcbb";
# nord8 = mkLiteral "#88c0d0";
# nord9 = mkLiteral "#81a1c1";
# nord10 = mkLiteral "#5e81ac";
# nord11 = mkLiteral "#bf616a";
# nord12 = mkLiteral "#d08770";
# nord13 = mkLiteral "#ebcb8b";
# nord14 = mkLiteral "#a3be8c";
# nord15 = mkLiteral "#b48ead";
# spacing = 2;
# background-color = mkLiteral "var(nord1)";
# background = mkLiteral "var(nord1)";
# foreground = mkLiteral "var(nord4)";
# normal-background = mkLiteral "var(background)";
# normal-foreground = mkLiteral "var(foreground)";
# alternate-normal-background = mkLiteral "var(background)";
# alternate-normal-foreground = mkLiteral "var(foreground)";
# selected-normal-background = mkLiteral "var(nord8)";
# selected-normal-foreground = mkLiteral "var(background)";
# active-background = mkLiteral "var(background)";
# active-foreground = mkLiteral "var(nord10)";
# alternate-active-background = mkLiteral "var(background)";
# alternate-active-foreground = mkLiteral "var(nord10)";
# selected-active-background = mkLiteral "var(nord10)";
# selected-active-foreground = mkLiteral "var(background)";
# urgent-background = mkLiteral "var(background)";
# urgent-foreground = mkLiteral "var(nord11)";
# alternate-urgent-background = mkLiteral "var(background)";
# alternate-urgent-foreground = mkLiteral "var(nord11)";
# selected-urgent-background = mkLiteral "var(nord11)";
# selected-urgent-foreground = mkLiteral "var(background)";
# };
#
# element = {
# padding = mkLiteral "0px 0px 0px 7px";
# spacing = mkLiteral "5px";
# border = 0;
# cursor = mkLiteral "pointer";
# };
# "element normal.normal" = {
# background-color = mkLiteral "var(normal-background)";
# text-color = mkLiteral "var(normal-foreground)";
# };
# "element normal.urgent" = {
# background-color = mkLiteral "var(urgent-background)";
# text-color = mkLiteral "var(urgent-foreground)";
# };
# "element normal.active" = {
# background-color = mkLiteral "var(active-background)";
# text-color = mkLiteral "var(active-foreground)";
# };
# "element selected.normal" = {
# background-color = mkLiteral "var(selected-normal-background)";
# text-color = mkLiteral "var(selected-normal-foreground)";
# };
# "element selected.urgent" = {
# background-color = mkLiteral "var(selected-urgent-background)";
# text-color = mkLiteral "var(selected-urgent-foreground)";
# };
# "element selected.active" = {
# background-color = mkLiteral "var(selected-active-background)";
# text-color = mkLiteral "var(selected-active-foreground)";
# };
# "element alternate.normal" = {
# background-color = mkLiteral "var(alternate-normal-background)";
# text-color = mkLiteral "var(alternate-normal-foreground)";
# };
# "element alternate.urgent" = {
# background-color = mkLiteral "var(alternate-urgent-background)";
# text-color = mkLiteral "var(alternate-urgent-foreground)";
# };
# "element alternate.active" = {
# background-color = mkLiteral "var(alternate-active-background)";
# text-color = mkLiteral "var(alternate-active-foreground)";
# };
# "element-text" = {
# background-color = mkLiteral "rgba(0, 0, 0, 0%)";
# text-color = mkLiteral "inherit";
# highlight = mkLiteral "inherit";
# cursor = mkLiteral "inherit";
# };
# "element-icon" = {
# background-color = mkLiteral "rgba(0, 0, 0, 0%)";
# size = mkLiteral "1.0000em";
# text-color = mkLiteral "inherit";
# cursor = mkLiteral "inherit";
# };
# window = {
# padding = 0;
# border = 0;
# background-color = mkLiteral "var(background)";
# };
# mainbox = {
# padding = 0;
# border = 0;
# };
# message = {
# margin = mkLiteral "0px 7px";
# };
# textbox = {
# text-color = mkLiteral "var(foreground)";
# };
# listview = {
# margin = mkLiteral "0px 0px 5px";
# scrollbar = true;
# spacing = mkLiteral "2px";
# fixed-height = 0;
# };
# scrollbar = {
# padding = 0;
# handle-width = mkLiteral "14px";
# border = 0;
# handle-color = mkLiteral "var(nord3)";
# };
# button = {
# spacing = 0;
# text-color = mkLiteral "var(normal-foreground)";
# cursor = mkLiteral "pointer";
# };
# "button selected" = {
# background-color = mkLiteral "var(selected-normal-background)";
# text-color = mkLiteral "var(selected-normal-foreground)";
# };
# inputbar = {
# padding = mkLiteral "7px";
# margin = mkLiteral "7px";
# spacing = 0;
# text-color = mkLiteral "var(normal-foreground)";
# background-color = mkLiteral "var(nord3)";
# children = [ "entry" ];
# };
# entry = {
# spacing = 0;
# cursor = mkLiteral "text";
# text-color = mkLiteral "var(normal-foreground)";
# background-color = mkLiteral "var(nord3)";
# };
#};
}

View file

@ -0,0 +1,184 @@
{ pkgs, lib, config, monitors ? [], ... }:
with lib;
let
cfg = config.modules.user.gui.wm.sway;
modifier = config.wayland.windowManager.sway.config.modifier;
wallpaper = builtins.fetchurl {
url = "https://images6.alphacoders.com/117/1174033.png";
sha256 = "1ph5m9s57076jx6042iipqx2ifzadmd5z4lf5l49wgq4jb92mp16";
};
barStatus = pkgs.writeShellScript "status.sh" ''
#!/usr/bin/env bash
while :; do
echo "$(ip -4 addr show eno1 | awk '/inet / {print $2}' | cut -d'/' -f1) | $(free -h | awk '/^Mem/ {print $3}') | $(date +'%I:%M:%S %p') | $(date +'%m-%d-%Y')"; sleep 1;
done
'';
toSwayOutput = m: {
"${m.name}" = {
resolution = "${toString m.width}x${toString m.height}@${toString m.refreshRate}Hz";
position = "${toString m.x} ${toString m.y}";
scale = toString m.scale;
bg = "${wallpaper} fill";
};
};
outputConfig = if monitors != []
then lib.mkMerge (map toSwayOutput monitors)
else {
"*" = { bg = "${wallpaper} fill"; };
};
in
{ options.modules.user.gui.wm.sway = { enable = mkEnableOption "Enable Sway WM"; };
config = mkIf cfg.enable {
wayland.windowManager.sway = {
enable = true;
xwayland = true;
wrapperFeatures.gtk = true;
extraSessionCommands = ''
export _JAVA_AWT_WM_NONREPARENTING=1
export GTK_THEME=Adwaita-Dark
'';
config = {
defaultWorkspace = "workspace number 1";
fonts = {
names = [ "Terminus" ];
};
output = outputConfig;
modifier = "Mod1";
menu = "rofi -show drun -show-icons -drun-icon-theme Qogir -font 'Noto Sans 14'";
terminal = "${pkgs.alacritty}/bin/alacritty";
input = {
keyboard = {
xkb_numlock = "enabled";
xkb_layout = "us";
};
pointer = {
accel_profile = "flat";
pointer_accel = "0.65";
};
};
bars = [
{
position = "top";
statusCommand = "${barStatus}";
fonts = {
names = [ "Terminus" ];
size = 12.0;
};
colors = {
background = "#0A0E14";
statusline = "#FFFFFF";
};
}
];
gaps = {
smartGaps = false;
inner = 10;
};
floating = {
titlebar = false;
border = 0;
criteria = [
{
title = "Android Emulator";
}
];
};
window = {
titlebar = false;
border= 0;
};
keybindings = lib.mkOptionDefault {
"${modifier}+q" = "kill";
"Print" = "exec grim ~/Pictures/screenshot-$(date +'%Y%m%d-%H%M%S').png";
"${modifier}+Shift+Print" = "exec grim -g \"$(slurp)\" ~/Pictures/screenshot-$(date +'%Y%m%d-%H%M%S').png";
"${modifier}+Print" = ''exec sh -c 'grim -g "$(swaymsg -t get_tree | jq -j '"'"'.. | select(.type?) | select(.focused).rect | "\(.x),\(.y) \(.width)x\(.height)"'"'"')" ~/Pictures/screenshot-$(date +'%Y%m%d-%H%M%S').png' '';
"${modifier}+Shift+f" = "exec alacritty -e sh -c 'EDITOR=nvim ranger'";
"${modifier}+Shift+d" = "exec rofi -modi emoji -show emoji";
};
};
extraConfig = ''
exec_always ${pkgs.autotiling}/bin/autotiling -sr "1.61"
'';
};
programs.rofi = import ./config/rofi { inherit pkgs config lib; };
home.file.".config/rofi" = {
source = ./config/rofi/config;
recursive = true;
};
xdg = {
portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-wlr
];
config.common.default = "*";
};
};
#gtk = {
# enable = true;
# theme.package = pkgs.juno-theme;
# theme.name = "Juno-ocean";
# iconTheme.package = pkgs.qogir-icon-theme;
# iconTheme.name = "Qogir";
#};
qt = {
enable = true;
style.package = pkgs.juno-theme;
platformTheme.name = "gtk";
};
home.packages = with pkgs; [
pavucontrol
xdg-utils
wl-clipboard
autotiling
grim
jq
slurp
ranger
highlight
nerd-fonts.terminess-ttf
noto-fonts
noto-fonts-cjk-sans
noto-fonts-color-emoji
];
programs = {
imv.enable = true;
};
fonts.fontconfig.enable = true;
# Auto-start sway on tty1
programs.bash.profileExtra = ''
if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
exec sway
fi
'';
};
}

View file

@ -0,0 +1,5 @@
{
"diagnostics.disable": [
"missing-fields"
]
}

View file

@ -0,0 +1,23 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.user.neovim;
in
{ options.modules.user.neovim = { enable = mkEnableOption "user.neovim"; };
config = mkIf cfg.enable {
programs.neovim = {
enable = true;
defaultEditor = true;
vimAlias = true;
vimdiffAlias = true;
extraPackages = import ./pkgs.nix { inherit pkgs; };
};
home.file.".config/nvim" = {
source = ./nvim;
recursive = true;
};
};
}

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