refactor: reorganize flake structure and consolidate user config

Directory structure:
- Move from src/ to root level (system/, user/)
- Remove unused machines (workstation, vm, laptop)

User configuration:
- Add user/home.nix for shared defaults (pass, essentials, default modules)
- Centralize user options in user/default.nix
- Move submodules to consistent paths (bash/bash, git/git, neovim/nvim, vim/vim)

Module reorganization:
- Flatten nested module structures (remove /modules/ subdirs)
- Split CLI vs GUI tools (dev/ for CLI, gui/dev/ for GUI)
- Move neovim/vim to top-level modules (not under utils/)
- Remove security.enable - pass now in user/home.nix
- Remove utils.enable - essentials now in user/home.nix
- Add security/yubikey module with yubikey-manager, age-plugin-yubikey
- Move pcb, design to gui/dev/
- Replace penpot docker wrapper with nixpkgs penpot-desktop
- Remove i3 config
- Remove deprecated wsl.nativeSystemd option

GUI improvements:
- Browser-focused mimeApps in gui/default.nix
- Each WM handles its own auto-start via profileExtra

Cleanup:
- Update README with new structure
- Update justfile paths and valid systems
- Fix submodule paths in .gitmodules
This commit is contained in:
Bryan Ramos 2026-03-14 15:26:18 -04:00
parent ac95d1c23d
commit 14efa80cab
141 changed files with 505 additions and 1561 deletions

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 ./.;
};
};
};
}

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOYXfu4Jc/HtdyhOfAdCXYzhqCubIq3Bz6Kl9NDUov76 bryan@desktop

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,14 @@
{ inputs, ... }:
{
imports = [
inputs.disko.nixosModules.disko
(import ./modules/disko)
inputs.home-manager.nixosModules.home-manager
(import ./modules/home-manager)
../../../user
../../keys
./hardware.nix
./system.nix
];
}

View file

@ -0,0 +1,83 @@
{ 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
];
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;
};
};
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,54 @@
{ config, ... }:
{
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";
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,148 @@
{ 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";
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;
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,14 @@
{ inputs, ... }:
{
imports = [
inputs.disko.nixosModules.disko
(import ./modules/disko)
inputs.home-manager.nixosModules.home-manager
(import ./modules/home-manager)
../../../user
../../keys
./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,119 @@
# Frigate Camera Setup
## Camera Models
| Camera | Model | MAC | IP |
|--------|-------|-----|-----|
| parking_lot | W461ASC | 00:1f:54:c2:d1:b1 | 192.168.1.194 |
| doorbell | B463AJ | 00:1f:54:a9:81:d1 | 192.168.1.167 |
| living_room | W463AQ (ch1) | 00:1f:54:b2:9b:1d | 192.168.1.147 |
| kitchen | W463AQ (ch2) | 00:1f:54:b2:9b:1d | 192.168.1.147 |
| porch | 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 (parking_lot)
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 (doorbell)
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 (living_room/kitchen)
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 (porch)
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
Frigate data is stored on /data to avoid filling root partition:
| Path | Bind Mount | Contents |
|------|------------|----------|
| /var/lib/frigate | /data/frigate/lib | Database, recordings, clips |
| /var/cache/frigate | /data/frigate/cache | Temporary cache |
| /var/cache/nginx/frigate | /data/frigate/nginx-cache | API response cache |
## 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

View file

@ -0,0 +1,105 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.modules.system.frigate;
nginx = config.modules.system.nginx;
domain = "ramos.codes";
in
{
options.modules.system.frigate = {
enable = mkEnableOption "Enable Frigate NVR";
};
config = mkIf cfg.enable {
services.frigate = {
enable = true;
hostname = "frigate.${domain}";
# vaapiDriver = "i965"; # Haswell only supports H.264, not HEVC
settings = {
mqtt.enabled = false;
# ffmpeg.hwaccel_args = "preset-vaapi"; # Disabled - camera uses HEVC which Haswell can't decode
record = {
enabled = true;
# 24/7 recording - needs better hardware
# retain = {
# days = 14;
# mode = "all";
# };
};
cameras = {
doorbell = {
enabled = true;
detect.enabled = false;
ffmpeg.inputs = [{
path = "rtsp://admin:ocu?u3Su@192.168.1.167/cam/realmonitor?channel=1&subtype=0";
roles = [ "record" ];
}];
};
living_room = {
enabled = false;
detect.enabled = false;
ffmpeg.inputs = [{
path = "rtsp://admin:ocu?u3Su@192.168.1.147/cam/realmonitor?channel=1&subtype=0";
roles = [ "record" ];
}];
};
kitchen = {
enabled = false;
detect.enabled = false;
ffmpeg.inputs = [{
path = "rtsp://admin:ocu?u3Su@192.168.1.147/cam/realmonitor?channel=2&subtype=0";
roles = [ "record" ];
}];
};
parking_lot = {
enabled = true;
detect.enabled = false;
ffmpeg.inputs = [{
path = "rtsp://admin:ocu?u3Su@192.168.1.194/cam/realmonitor?channel=1&subtype=0";
roles = [ "record" ];
}];
};
porch = {
enabled = false;
detect.enabled = false;
ffmpeg.inputs = [{
path = "rtsp://admin:ocu?u3Su@192.168.0.43/cam/realmonitor?channel=1&subtype=0";
roles = [ "record" ];
}];
};
};
};
};
# Add SSL to frigate's nginx virtualHost
services.nginx.virtualHosts."frigate.${domain}" = mkIf nginx.enable {
useACMEHost = domain;
forceSSL = true;
};
# Bind mount caches into the 3TB frigate LVM volume
systemd.tmpfiles.rules = [
"d /var/lib/frigate/cache 0750 frigate frigate -"
"d /var/lib/frigate/nginx-cache 0750 nginx nginx -"
];
fileSystems."/var/cache/frigate" = {
device = "/var/lib/frigate/cache";
options = [ "bind" ];
};
fileSystems."/var/cache/nginx/frigate" = {
device = "/var/lib/frigate/nginx-cache";
options = [ "bind" ];
};
# Backup recordings/database, exclude caches
modules.system.backup = {
paths = [ "/var/lib/frigate" ];
exclude = [ "*/cache" "*/nginx-cache" ];
};
};
}

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,185 @@
{ pkgs, lib, config, ... }:
{ system.stateVersion = "25.11";
imports = [ ./modules ];
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 specific camera MACs from forwarding (instant DROP, no timeouts)
# Add each camera MAC here as you set them up
iptables -A FORWARD -m mac --mac-source 00:1f:54:c2:d1:b1 -j DROP # parking_lot
iptables -A FORWARD -m mac --mac-source 00:1f:54:b2:9b:1d -j DROP # living_room/kitchen
iptables -A FORWARD -m mac --mac-source 00:1f:54:a9:81:d1 -j DROP # doorbell
'';
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,parking_lot"
"00:1f:54:b2:9b:1d,192.168.1.147,living_room_kitchen"
"00:1f:54:a9:81:d1,192.168.1.167,doorbell"
];
};
};
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,13 @@
{ inputs, ... }:
{
imports = [
inputs.nixos-wsl.nixosModules.wsl
(import ./modules/wsl)
inputs.home-manager.nixosModules.home-manager
(import ./modules/home-manager)
../../../user
../../keys
./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"
];
};
};
}