NixOS Configuration
Table of Contents
- 1. Introduction
- 2. Configurables
- 3. Flake.nix
- 4. Sops Configuration
- 5. Nix DNS
- 6. Nix Topology
- 7. Modules
- 7.1. Vars
- 7.2. Default Profile
- 7.3. Secrets
- 7.4. X11
- 7.5. Containers
- 7.6. Pipewire
- 7.7. SSH
- 7.8. Tor
- 7.9. Kubo IPFS
- 7.10. TODO i2pd
- 7.11. TODO Icecast
- 7.12. IRC
- 7.13. Conduit
- 7.14. GoToSocial
- 7.15. Matterbridge
- 7.16. TODO matrix-appservice-irc
- 7.17. Ollama
- 7.18. Bitcoind
- 7.19. Git Server
- 7.20. Ntfy
- 7.21. Nginx
- 7.22. Git Web Interface
- 7.23. Nvidia
- 7.24. CUDA
- 7.25. Maddy
- 7.26. Fail2Ban
- 7.27. Impermanence
- 7.28. Main Configuration
- 7.29. Disko
- 7.30. Home
- 7.30.1. Default Home Profile
- 7.30.2. Secrets
- 7.30.3. Firefox
- 7.30.4. Fcitx
- 7.30.5. Emacs
- 7.30.6. Gammastep
- 7.30.7. Git
- 7.30.8. Hyprland
- 7.30.9. Kitty
- 7.30.10. Mako
- 7.30.11. Mbsync
- 7.30.12. MSMTP
- 7.30.13. Mpd
- 7.30.14. MPV
- 7.30.15. GTK
- 7.30.16. Waybar
- 7.30.17. Wofi
- 7.30.18. yt-dlp
- 7.30.19. Zsh
- 7.30.20. User
- 8. Systems
1. Introduction
This is my NixOS configuration. It is a part of my monorepo, and this file automatically tangles to all the under the nix/ directory in my monorepo git repository. My monorepo also stores my website, as my website stores my elfeed and emacs configurations. Additionally, I want to track my emacs configuration with my Nix configuration. Having them in one repository means that my emacs configuration is pinned to my flake.
Hence, my monorepo serves a dual purpose, as do many of the files within my monorepo. They are often data files used in my configuration (i.e. emacs, elfeed, org-roam, agenda, journal, etc…) and they are webpages as well. This page is one such example of this concept.
2. Configurables
We start with some configurable variables (you can change these if you want to use this configuration yourself):
let # I'm ret2pop! What's your name? internetName = "ret2pop"; in { # Name of spontaneity box remoteHost = "${internetName}.net"; # Your internet name internetName = internetName; # Name of your organization orgHost = "nullring.xyz"; # Hostnames of my systems hostnames = [ "affinity" "continuity" "spontaneity" "installer" ]; }
3. Flake.nix
The flake is the entry point of the NixOS configuration. Here, I have a list of all the systems that I use with all the modules that they use. My NixOS configuration is heavily modularized, so that adding new configurations that add modifications is made simple.
and now for the main flake:
{
description = "Emacs centric configurations for a complete networked system";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nur.url = "github:nix-community/NUR";
sops-nix.url = "github:Mic92/sops-nix";
scripts.url = "github:ret2pop/scripts";
wallpapers.url = "github:ret2pop/wallpapers";
sounds.url = "github:ret2pop/sounds";
deep-research.url = "github:ret2pop/ollama-deep-researcher";
impermanence.url = "github:nix-community/impermanence";
nix-topology = {
url = "github:oddlama/nix-topology";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
lanzaboote = {
url = "github:nix-community/lanzaboote/v0.4.1";
inputs.nixpkgs.follows = "nixpkgs";
};
nixos-dns = {
url = "github:Janik-Haag/nixos-dns";
inputs.nixpkgs.follows = "nixpkgs";
};
nixpak = {
url = "github:nixpak/nixpak";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
home-manager,
nur,
disko,
lanzaboote,
sops-nix,
nix-topology,
nixos-dns,
deep-research,
impermanence,
nixpak,
...
}
@attrs:
let
vars = import ./flakevars.nix;
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
generate = nixos-dns.utils.generate nixpkgs.legacyPackages."${system}";
dnsConfig = {
inherit (self) nixosConfigurations;
extraConfig = import ./dns/default.nix;
};
# function that generates all systems from hostnames
mkConfigs = map (hostname: {name = "${hostname}";
value = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = attrs;
modules = if (hostname == "installer") then [
(./. + "/systems/${hostname}/default.nix")
{ networking.hostName = "${hostname}"; }
nix-topology.nixosModules.default
] else [
{
environment.systemPackages = with nixpkgs.lib; [
deep-research.packages."${system}".deep-research
];
}
impermanence.nixosModules.impermanence
nix-topology.nixosModules.default
lanzaboote.nixosModules.lanzaboote
disko.nixosModules.disko
home-manager.nixosModules.home-manager
sops-nix.nixosModules.sops
nixos-dns.nixosModules.dns
{
nixpkgs.overlays = [ nur.overlays.default ];
home-manager.extraSpecialArgs = attrs // { systemHostName = "${hostname}"; };
networking.hostName = "${hostname}";
}
(./. + "/systems/${hostname}/default.nix")
];
};
});
mkDiskoFiles = map (hostname: {
name = "${hostname}";
value = self.nixosConfigurations."${hostname}".config.monorepo.vars.diskoSpec;
});
in
{
nixosConfigurations = builtins.listToAttrs (mkConfigs vars.hostnames);
evalDisko = builtins.listToAttrs (mkDiskoFiles (builtins.filter (x: x != "installer") vars.hostnames));
topology."${system}" = import nix-topology {
pkgs = import nixpkgs {
inherit system;
overlays = [ nix-topology.overlays.default ];
};
modules = [
./topology/default.nix
{ nixosConfigurations = self.nixosConfigurations; }
];
};
devShell."${system}" = with pkgs; mkShell {
buildInputs = [
fira-code
python3
poetry
statix
deadnix
];
};
packages."${system}" = {
zoneFiles = generate.zoneFiles dnsConfig;
octodns = generate.octodnsConfig {
inherit dnsConfig;
config = {
providers = {
cloudflare = {
class = "octodns_cloudflare.CloudflareProvider";
token = "env/CLOUDFLARE_TOKEN";
};
config = {
check_origin = false;
};
};
};
zones = {
"${vars.remoteHost}." = nixos-dns.utils.octodns.generateZoneAttrs [ "cloudflare" ];
"${vars.orgHost}." = nixos-dns.utils.octodns.generateZoneAttrs [ "cloudflare" ];
};
};
};
};
}
Note that the configurations are automatically generated with he mkConfigs function, and the final disko output is automatically generated with mkDiskoFiles.
4. Sops Configuration
In order to use the sops configuration, you must change the age public key to the one that you own:
keys: - &primary age165ul43e8rc0qwzz2f2q9cw02psm2mkudsrwavq2e0pxs280p64yqy2z0dr - &vps age1acpuyy2qnduyxzwvusd8urr6a78e3f37ylhvh2pngyqytf5r8ans5vkest creation_rules: - path_regex: secrets/secrets.yaml$ key_groups: - age: - *primary - path_regex: secrets/vps_secrets.yaml$ key_groups: - age: - *vps
also note that you will have to write your own secrets.yaml file, with an entry called mail,
which is used for the imaps and smtps password.
5. Nix DNS
{
defaultTTL = 120;
}
6. Nix Topology
Nix Topology generates a nice graph of all my hosts. You can view this
graph by running nix build .#topology.x86_64-linux.config.output.
{ config, ... }:
let
inherit
(config.lib.topology);
in
{
nodes = {
spontaneity = {
interfaces.wan.network = "remote";
};
installer = {
interfaces.lan.network = "home";
};
affinity = {
interfaces.lan = {
network = "home";
physicalConnections = [
{
node = "spontaneity";
interface = "wan";
}
{
node = "installer";
interface = "lan";
}
];
};
};
continuity = {
interfaces.lan = {
network = "home";
physicalConnections = [
{
node = "spontaneity";
interface = "wan";
}
{
node = "affinity";
interface = "lan";
}
];
};
};
};
networks = {
home = {
name = "Home Network";
cidrv4 = "192.168.1.1/24";
};
remote = {
name = "Remote Network";
cidrv4 = "144.202.27.169/32";
};
};
}
7. Modules
7.1. Vars
Variables used for regular configuration in your system default.nix file. The options are
largely self-documenting.
{ lib, ... }:
let
vars = import ../flakevars.nix;
in
{
options.monorepo.vars = {
device = lib.mkOption {
type = lib.types.str;
default = "/dev/sda";
example = "/dev/nvme0n1";
description = "device that NixOS is installed to";
};
internetName = lib.mkOption {
type = lib.types.str;
default = "${vars.internetName}";
example = "myinternetname";
description = "Internet name to be used for internet usernames";
};
repoName = lib.mkOption {
type = lib.types.str;
default = "monorepo";
example = "myreponame";
description = "Name of this repository";
};
fileSystem = lib.mkOption {
type = lib.types.str;
default = "ext4";
example = "btrfs";
description = "filesystem to install with disko";
};
diskoSpec = lib.mkOption {
type = lib.types.attrs;
description = "retains a copy of the disko spec for reflection";
};
userName = lib.mkOption {
type = lib.types.str;
default = "preston";
example = "myUser";
description = "system username";
};
fullName = lib.mkOption {
type = lib.types.str;
default = "Preston Pan";
example = "John Doe";
description = "Full Name";
};
gpgKey = lib.mkOption {
type = lib.types.str;
default = "AEC273BF75B6F54D81343A1AC1FE6CED393AE6C1";
example = "1234567890ABCDEF...";
description = "GPG key fingerprint";
};
remoteHost = lib.mkOption {
type = lib.types.str;
default = "${vars.remoteHost}";
example = "example.com";
description = "Address to push to and pull from for website and git repos";
};
orgHost = lib.mkOption {
type = lib.types.str;
default = "${vars.orgHost}";
example = "orgname.org";
description = "Domain name of your organization";
};
timeZone = lib.mkOption {
type = lib.types.str;
default = "America/Vancouver";
example = "America/Chicago";
description = "Linux timezone";
};
monitors = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"HDMI-A-1"
"eDP-1"
"DP-2"
"DP-3"
"DP-4"
"LVDS-1"
];
example = [];
description = "Monitors that waybar will use";
};
};
}
7.2. Default Profile
Again, these are self documenting variables that you may see used below. These are to be used
under default.nix in the systems folder.
{ lib, config, pkgs, ... }:
{
imports = [
./configuration.nix
./vars.nix
];
options = {
monorepo = {
profiles = {
cuda.enable = lib.mkEnableOption "Enables CUDA support";
documentation.enable = lib.mkEnableOption "Enables documentation on system.";
secureBoot.enable = lib.mkEnableOption "Enables secure boot. See sbctl.";
pipewire.enable = lib.mkEnableOption "Enables pipewire low latency audio setup";
tor.enable = lib.mkEnableOption "Enables tor along with torsocks";
home.enable = lib.mkEnableOption "Enables home user";
server.enable = lib.mkEnableOption "Enables server services";
ttyonly.enable = lib.mkEnableOption "TTY only, no xserver";
grub.enable = lib.mkEnableOption "Enables grub instead of systemd-boot";
workstation.enable = lib.mkEnableOption "Enables workstation services";
impermanence.enable = lib.mkEnableOption "Enables imperamanence";
};
};
};
config = {
environment.systemPackages = lib.mkIf config.monorepo.profiles.documentation.enable ((with pkgs; [
linux-manual
man-pages
man-pages-posix
iproute2
silver-searcher
ripgrep
]) ++
(if (config.monorepo.vars.fileSystem == "btrfs") then with pkgs; [
btrfs-progs
btrfs-snap
btrfs-list
btrfs-heatmap
] else []));
boot.loader.grub = lib.mkIf config.monorepo.profiles.grub.enable {
enable = true;
};
monorepo = {
profiles = {
documentation.enable = lib.mkDefault true;
pipewire.enable = lib.mkDefault true;
tor.enable = lib.mkDefault true;
home.enable = lib.mkDefault true;
impermanence.enable = lib.mkDefault false;
};
};
};
}
7.3. Secrets
This uses sops in order to declaratively create the secrets on my system by unencrypting the yaml file specified. Yes, this is safe to include in the repo.
{ config, ... }:
{
sops = {
defaultSopsFile = if config.monorepo.profiles.server.enable
then ../secrets/vps_secrets.yaml
else ../secrets/secrets.yaml;
templates = if config.monorepo.profiles.server.enable then {
"public-inbox-netrc" = {
owner = "public-inbox";
group = "public-inbox";
mode = "0400";
content = ''
machine mail.${config.monorepo.vars.orgHost} login monorepo@${config.monorepo.vars.orgHost} password ${config.sops.placeholder."mail_monorepo_password_pi"}
machine mail.${config.monorepo.vars.orgHost} login discussion@${config.monorepo.vars.orgHost} password ${config.sops.placeholder."mail_monorepo_password_pi"}
'';
};
"matterbridge" = {
owner = "matterbridge";
content = ''
[irc.myirc]
Server="127.0.0.1:6667"
Nick="bridge"
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
UseTLS=false
[telegram.mytelegram]
Token="${config.sops.placeholder.telegram_token}"
RemoteNickFormat="<({PROTOCOL}){NICK}> "
MessageFormat="HTMLNick :"
QuoteFormat="{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
QuoteLengthLimit=46
IgnoreMessages="^/"
[discord.mydiscord]
Token="${config.sops.placeholder.discord_token}"
Server="Null Identity"
AutoWebHooks=true
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
PreserveThreading=true
[[gateway]]
name="gateway1"
enable=true
[[gateway.inout]]
account="irc.myirc"
channel="#nullring"
[[gateway.inout]]
account="discord.mydiscord"
channel="ID:996282946879242262"
[[gateway.inout]]
account="telegram.mytelegram"
channel="-5290629325"
'';
};
} else {};
age = {
keyFile = "/home/${config.monorepo.vars.userName}/.config/sops/age/keys.txt";
};
secrets = if ! config.monorepo.profiles.server.enable then {
mail = {
format = "yaml";
};
cloudflare-dns = {
format = "yaml";
};
digikey = {
format = "yaml";
};
dn42 = {
format = "yaml";
};
} else {
znc = {
format = "yaml";
};
znc_password_salt = {
format = "yaml";
};
znc_password_hash = {
format = "yaml";
};
matrix_bridge = {
format = "yaml";
};
livekit_secret = {
format = "yaml";
mode = "0444";
};
livekit = {
format = "yaml";
};
mail_password = {
format = "yaml";
owner = "maddy";
};
mail_monorepo_password = {
format = "yaml";
owner = "maddy";
};
mail_monorepo_password_pi = {
format = "yaml";
owner = "public-inbox";
};
conduit_secrets = {
format = "yaml";
};
mautrix_env = {
format = "yaml";
};
telegram_token = {
format = "yaml";
};
discord_token = {
format = "yaml";
};
mpd_password = {
format = "yaml";
owner = "nginx";
};
ntfy = {
format = "yaml";
owner = "ntfy-sh";
};
};
};
}
7.4. X11
My Xorg configuration is used as a backup for when wayland applications don't work. Note that using this configuration is extremely inefficient and my i3 configuration is unoptimized. Still, it is suitable for using Krita.
{ lib, config, pkgs, ... }:
{
services.xserver = {
enable = (! config.monorepo.profiles.ttyonly.enable);
displayManager = {
startx.enable = (! config.monorepo.profiles.ttyonly.enable);
};
windowManager = {
i3 = {
enable = (! config.monorepo.profiles.ttyonly.enable);
};
};
desktopManager = {
runXdgAutostartIfNone = true;
};
xkb = {
layout = "us";
variant = "";
options = "caps:escape";
};
videoDrivers = (if config.monorepo.profiles.cuda.enable then [ "nvidia" ] else []);
};
}
You should add your own video drivers in a custom machine configuration.
7.5. Containers
In order to run docker/podman containers, I need this file:
{ lib, config, vars, ... }:
{
# virtualisation = {
# oci-containers = {
# backend = "podman";
# containers = {};
# };
# containers.enable = true;
# podman = {
# enable = true;
# dockerCompat = true;
# defaultNetwork.settings.dns_enabled = true;
# };
# };
}
7.6. Pipewire
My low latency pipewire configuration is used for music production, as well as for regular desktop usage. Pipewire is much better than pulseaudio because it supports jack with the same underlying interface and it breaks significantly less often.
{ lib, config, ... }:
{
services.pipewire = {
enable = lib.mkDefault config.monorepo.profiles.pipewire.enable;
alsa = {
enable = lib.mkDefault config.monorepo.profiles.pipewire.enable;
support32Bit = true;
};
pulse.enable = lib.mkDefault config.monorepo.profiles.pipewire.enable;
jack.enable = lib.mkDefault config.monorepo.profiles.pipewire.enable;
wireplumber.enable = lib.mkDefault config.monorepo.profiles.pipewire.enable;
extraConfig = {
pipewire."92-low-latency" = {
"context.properties" = {
"default.clock.rate" = 48000;
"default.clock.quantum" = 512;
"default.clock.min-quantum" = 512;
"default.clock.max-quantum" = 1024;
};
pipewire-pulse."92-low-latency" = {
"context.properties" = [
{
name = "libpipewire-module-protocol-pulse";
args = { };
}
];
"pulse.properties" = {
"pulse.min.req" = "32/48000";
"pulse.default.req" = "32/48000";
"pulse.max.req" = "32/48000";
"pulse.min.quantum" = "32/48000";
"pulse.max.quantum" = "32/48000";
};
"stream.properties" = {
"node.latency" = "32/48000";
"resample.quality" = 1;
};
};
};
};
};
}
7.7. SSH
My SSH daemon configuration.
{ config, lib, ... }:
{
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = lib.mkDefault (! config.monorepo.profiles.server.enable);
AllowUsers = [ config.monorepo.vars.userName "root" "git" ];
PermitRootLogin = "prohibit-password";
KbdInteractiveAuthentication = false;
};
};
}
7.8. Tor
This is my tor configuration, used for my cryptocurrency wallets and whatever else I want it to do.
{ config, lib, ... }:
{
services.tor = {
enable = lib.mkDefault config.monorepo.profiles.tor.enable;
openFirewall = true;
client = {
enable = lib.mkDefault config.monorepo.profiles.tor.enable;
socksListenAddress = {
IsolateDestAddr = true;
addr = "127.0.0.1";
port = 9050;
};
dns.enable = true;
};
torsocks = {
enable = lib.mkDefault config.monorepo.profiles.tor.enable;
server = "127.0.0.1:9050";
};
};
}
7.9. Kubo IPFS
I use IPFS for my website and also for my ISOs for truly declarative and deterministic configuration. NixOS might be moving to IPFS for binary cache distribution and package distribution soon, and I'm waiting on that.
{ config, pkgs, lib, ... }:
{
services.kubo = {
enable = lib.mkDefault config.monorepo.profiles.workstation.enable;
autoMount = false;
enableGC = true;
settings = {
Addresses.API = [
"/ip4/127.0.0.1/tcp/5001"
];
Bootstrap = [
"/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
"/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
];
Datastore = {
StorageMax = "20GB";
};
};
};
}
7.10. TODO i2pd
I use i2p for some p2p connections. We enable it with the server profile:
{ config, lib, ... }:
{
services.i2pd = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
address = "0.0.0.0";
inTunnels = {
};
outTunnels = {
};
};
}
7.11. TODO Icecast
This is an internet radio which will host a ton of music.
{ lib, config, ... }:
{
services.icecast = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
listen.address = "0.0.0.0";
extraConfig = ''
<mount type="default">
<public>0</public>
<intro>/stream.m3u</intro>
<max-listener-duration>3600</max-listener-duration>
<authentication type="url">
<option name="mount_add" value="http://auth.example.org/stream_start.php"/>
</authentication>
<http-headers>
<header name="foo" value="bar" />
</http-headers>
</mount>
'';
};
admin.password = "changeme";
}
7.12. IRC
A great protocol. It's the most widely usable by any netizen, as it is just pure plaintext and the operating costs are trivial.
7.12.1. NgIRCD
I run my own IRC server to bridge with my Matrix server and my discord guild.
{ lib, config, ... }:
{
services.ngircd = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
config = ''
[Global]
Name = ${config.monorepo.vars.orgHost}
Info = NullRing IRC Instance
Listen = ::,0.0.0.0
MotdFile = /etc/motd.txt
Network = NullRing
Ports = 6667
[Options]
PAM = no
[SSL]
CertFile = /var/lib/acme/${config.monorepo.vars.orgHost}/fullchain.pem
CipherList = HIGH:!aNULL:@STRENGTH:!SSLv3
KeyFile = /var/lib/acme/${config.monorepo.vars.orgHost}/key.pem
Ports = 6697
'';
};
environment.etc."motd.txt" = {
source = ../data/motd.txt;
mode = "644";
user = "ngircd";
group = "ngircd";
};
}
7.12.2. MOTD
I also have a MOTD file that I want to add, which displays when users connect to the server:
Welcome to the NullRing experience! The main channel is #nullring; we're glad to have you! Rules: 1. Don't be annoying. 2. No illegal content. And if you're here to have constructive, philisophical and theoretical conversations, this is the place for you!
7.12.3. ZNC
I want to be able to create some sort of identity persistence on IRC for users:
{ lib, config, ... }:
{
services.znc = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
openFirewall = true;
confOptions = {
useSSL = true;
passBlock = ''
<Pass password>
Method = sha256
Hash = d4abdd69aa24de69693885c5bd83a4a0e9ee989e1a69a905041b0dad9abc06ea
Salt = sDY,?H5AxC-!gH3a.:)D
</Pass>
'';
modules = [
"partyline"
"webadmin"
"adminlog"
"log"
];
networks = {
"libera" = {
server = "irc.libera.chat";
port = 6697;
useSSL = true;
modules = [ "simple_away" ];
};
"nullring" = {
server = "nullring.xyz";
port = 6697;
useSSL = true;
modules = [ "simple_away" "log" ];
};
};
};
};
}
Note that the password hash and whatnot is completely random so there is almost no point to cracking it with hashcat.
7.13. Conduit
This is a modern matrix server that is meant to be lightweight while still federating and hosting the same protocol. There is also a configuration for lk-jwt and livekit which is important for configuring p2p calls in matrix.
{ config, lib, ... }:
{
services.matrix-conduit = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
secretFile = "/run/secrets/conduit_secrets";
settings.global = {
server_name = "matrix.${config.monorepo.vars.orgHost}";
trusted_servers = [
"matrix.org"
"nixos.org"
"conduit.rs"
];
address = "0.0.0.0";
port = 6167;
allow_registration = false;
};
};
services.lk-jwt-service = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
port = 6495;
livekitUrl = "wss://livekit.${config.monorepo.vars.orgHost}";
keyFile = "/run/secrets/livekit_secret";
};
services.livekit = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
keyFile = "/run/secrets/livekit_secret";
settings = {
port = 7880;
turn = {
enabled = true;
domain = "livekit.${config.monorepo.vars.orgHost}";
cert_file = "/var/lib/acme/livekit.${config.monorepo.vars.orgHost}/fullchain.pem";
key_file = "/var/lib/acme/livekit.${config.monorepo.vars.orgHost}/key.pem";
tls_port = 5349;
udp_port = 3478;
};
rtc = {
use_external_ip = true;
tcp_port = 7881;
udp_port = 7882;
port_range_start = 50000;
port_range_end = 60000;
};
};
};
}
7.14. GoToSocial
This is a basic ActivityPub server.
{ lib, config, ... }:
{
services.gotosocial = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
setupPostgresqlDB = true;
settings = {
application-name = "Nullring GoToSocial Instance";
host = "gotosocial.${config.monorepo.vars.orgHost}";
protocol = "https";
bind-address = "127.0.0.1";
port = 8080;
};
};
}
7.15. Matterbridge
I want to connect IRC to discord with matterbridge.
{ lib, config, ... }:
{
services.matterbridge = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
configPath = "${config.sops.templates.matterbridge.path}";
};
}
7.16. TODO matrix-appservice-irc
{ lib, config, ... }:
{
enable = lib.mkDefault config.monorepo.profiles.server.enable;
registrationUrl = "localhost";
settings = {
homeserver.url = "https://matrix.nullring.xyz";
homserver.domain = "matrix.nullring.xyz";
};
}
7.16.1. Mautrix
I use this bridge to bridge myself from Matrix to Discord and vise versa, because Matterbridge is not maintained very well and therefore
does not support conduit at the moment. Note that this is not fully declarative and requires that you add
/var/lib/mautrix-discord/discord-registration.yaml as an appservice to conduit.
{ lib, config, ... }:
{
services.mautrix-discord = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
environmentFile = "/run/secrets/mautrix_env";
settings = {
bridge = {
animated_sticker = {
args = {
fps = 25;
height = 320;
width = 320;
};
target = "webp";
};
autojoin_thread_on_open = true;
avatar_proxy_key = "generate";
backfill = {
forward_limits = {
initial = {
channel = 0;
dm = 0;
thread = 0;
};
max_guild_members = -1;
missed = {
channel = 0;
dm = 0;
thread = 0;
};
};
};
cache_media = "unencrypted";
channel_name_template = "{{if or (eq .Type 3) (eq .Type 4)}}{{.Name}}{{else}}#{{.Name}}{{end}}";
command_prefix = "!discord";
custom_emoji_reactions = true;
delete_guild_on_leave = true;
delete_portal_on_channel_delete = false;
delivery_receipts = false;
direct_media = {
allow_proxy = true;
enabled = false;
server_key = "generate";
};
displayname_template = "{{if .Webhook}}Webhook{{else}}{{or .GlobalName .Username}}{{if .Bot}} (bot){{end}}{{end}}";
double_puppet_allow_discovery = true;
double_puppet_server_map = { };
embed_fields_as_tables = true;
enable_webhook_avatars = true;
encryption = {
allow = false;
allow_key_sharing = false;
appservice = false;
default = false;
delete_keys = {
delete_fully_used_on_decrypt = false;
delete_on_device_delete = false;
delete_outbound_on_ack = false;
delete_outdated_inbound = false;
delete_prev_on_new_session = false;
dont_store_outbound = false;
periodically_delete_expired = false;
ratchet_on_decrypt = false;
};
msc4190 = false;
plaintext_mentions = false;
require = false;
rotation = {
disable_device_change_key_rotation = false;
enable_custom = false;
messages = 100;
milliseconds = 604800000;
};
verification_levels = {
receive = "unverified";
send = "unverified";
share = "cross-signed-tofu";
};
};
federate_rooms = true;
guild_name_template = "{{.Name}}";
login_shared_secret_map = { };
management_room_text = {
additional_help = "";
welcome = "Hello, I'm a Discord bridge bot.";
welcome_connected = "Use `help` for help.";
welcome_unconnected = "Use `help` for help or `login` to log in.";
};
message_error_notices = true;
message_status_events = false;
mute_channels_on_create = false;
permissions = {
"@${config.monorepo.vars.internetName}:matrix.${config.monorepo.vars.orgHost}" = "admin";
"*" = "user";
};
portal_message_buffer = 128;
prefix_webhook_messages = true;
private_chat_portal_meta = "default";
provisioning = {
debug_endpoints = false;
prefix = "/_matrix/provision";
shared_secret = "generate";
};
public_address = null;
resend_bridge_info = false;
restricted_rooms = false;
startup_private_channel_create_limit = 5;
sync_direct_chat_list = false;
use_discord_cdn_upload = true;
username_template = "discord_{{.}}";
};
appservice = {
address = "http://localhost:29334";
hostname = "0.0.0.0";
port = 29334;
id = "discord";
bot = {
username = "discordbot";
displayname = "Discord bridge bot";
avatar = "mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC";
};
ephemeral_events = true;
async_transactions = false;
database = {
type = "sqlite3";
uri = "file:${config.services.mautrix-discord.dataDir}/mautrix-discord.db?_txlock=immediate";
max_open_conns = 20;
max_idle_conns = 2;
max_conn_idle_time = null;
max_conn_lifetime = null;
};
as_token = "$MAUTRIX_DISCORD_APPSERVICE_AS_TOKEN";
hs_token = "$MAUTRIX_DISCORD_APPSERVICE_HS_TOKEN";
};
dataDir = "/var/lib/mautrix-discord";
homeserver = {
async_media = false;
message_send_checkpoint_endpoint = null;
ping_interval_seconds = 0;
software = "standard";
status_endpoint = null;
websocket = false;
domain = "matrix.${config.monorepo.vars.orgHost}";
address = "http://localhost:6167";
};
};
};
}
7.17. Ollama
Use ollama for serving large language models to my other computers.
{ config, lib, pkgs, ... }:
{
# services.open-webui.enable = lib.mkDefault (!config.monorepo.profiles.server.enable);
services.ollama = {
enable = lib.mkDefault (!config.monorepo.profiles.server.enable);
package = if (config.monorepo.profiles.workstation.enable) then pkgs.ollama-cuda else pkgs.ollama-vulkan;
loadModels = if (config.monorepo.profiles.workstation.enable) then [
"qwen3:30b"
"qwen3-coder:latest"
"qwen2.5-coder:latest"
"gemma3:12b-it-qat"
] else [
"qwen3:0.6b"
"qwen2.5-coder:0.5b"
];
host = "0.0.0.0";
openFirewall = true;
};
}
7.18. Bitcoind
{ config, lib, ... }:
{
services.bitcoind."${config.monorepo.vars.userName}" = {
enable = lib.mkDefault config.monorepo.profiles.workstation.enable;
prune = 10000;
};
}
7.19. Git Server
I run my own git server in order to have a mirror in case github goes down.
{ config, lib, ... }:
{
services.gitDaemon = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
exportAll = true;
basePath = "/srv/git";
};
}
7.20. Ntfy
{ pkgs, lib, config, ... }:
{
services.ntfy-sh = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
settings = {
base-url = "https://ntfy.${config.monorepo.vars.remoteHost}";
listen-http = "127.0.0.1:2586";
envrionmentFile = "/run/secrets/ntfy";
auth-file = "/var/lib/ntfy-sh/user.db";
auth-default-access = "deny-all";
enable-login = true;
};
};
systemd.services.ntfy-sh = {
serviceConfig = {
EnvironmentFile = "/run/secrets/ntfy";
};
postStart = lib.mkForce ''
# 1. Wait for the server to initialize the database
echo "Waiting for ntfy auth database to appear..."
TIMEOUT=30
while [ ! -f /var/lib/ntfy-sh/user.db ]; do
sleep 1
TIMEOUT=$((TIMEOUT-1))
if [ $TIMEOUT -le 0 ]; then
echo "Timed out waiting for database creation!"
exit 1
fi
done
echo "Database found. Configuring admin user..."
# 2. Define the username
ADMIN_USER="ret2pop"
# 3. Check if user exists, create if missing
# We pipe the password twice because 'ntfy user add' asks for confirmation
if ! ${pkgs.ntfy-sh}/bin/ntfy user list | grep -q "$ADMIN_USER"; then
echo "Creating admin user $ADMIN_USER..."
printf "$ADMIN_PASSWORD\n$ADMIN_PASSWORD" | \
${pkgs.ntfy-sh}/bin/ntfy user add --role=admin "$ADMIN_USER"
echo "User created."
else
echo "Admin user already exists."
fi
'';
};
}
7.21. Nginx
These are all my virtual hosts. For many of these servers we have to have a reverse proxy in order to expose the locally running instances to the outside world under a domain.
{ config, lib, ... }:
{
services.nginx = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
user = "nginx";
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedTlsSettings = true;
recommendedProxySettings = false;
gitweb = {
enable = true;
virtualHost = "${config.monorepo.vars.orgHost}";
};
virtualHosts = {
"matrix.${config.monorepo.vars.orgHost}" = {
enableACME = true;
forceSSL = true;
listen = [
{
addr = "0.0.0.0";
port = 443;
ssl = true;
}
{
addr = "[::]";
port = 443;
ssl = true;
}
{
addr = "0.0.0.0";
port = 8448;
ssl = true;
}
{
addr = "[::]";
port = 8448;
ssl = true;
}
];
locations."/_matrix/" = {
proxyPass = "http://127.0.0.1:6167";
extraConfig = ''
proxy_set_header Host $host;
proxy_buffers 32 16k;
proxy_read_timeout 5m;
'';
};
locations."= /.well-known/matrix/server" = {
extraConfig = ''
default_type application/json;
add_header Content-Type application/json;
add_header Access-Control-Allow-Origin *;
'';
return = ''200 '{"m.server": "matrix.${config.monorepo.vars.orgHost}:443"}' '';
};
locations."/.well-known/matrix/client" = {
extraConfig = ''
default_type application/json;
add_header Access-Control-Allow-Origin *;
'';
return = "200 '{\"m.homeserver\": {\"base_url\": \"https://matrix.${config.monorepo.vars.orgHost}\"}, \"org.matrix.msc4143.rtc_foci\": [{\"type\": \"livekit\", \"livekit_service_url\": \"https://matrix.${config.monorepo.vars.orgHost}:8443\"}]}'";
};
extraConfig = ''
merge_slashes off;
'';
};
"matrix.${config.monorepo.vars.orgHost}-livekit" = {
serverName = "matrix.${config.monorepo.vars.orgHost}";
listen = [
{
addr = "0.0.0.0";
port = 8443;
ssl = true;
}
{
addr = "[::]";
port = 8443;
ssl = true;
}
];
addSSL = true;
enableACME = false;
forceSSL = false;
useACMEHost = "matrix.${config.monorepo.vars.orgHost}";
locations."/" = {
proxyPass = "http://127.0.0.1:6495";
proxyWebsockets = true;
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;
'';
};
};
"livekit.${config.monorepo.vars.orgHost}" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:7880";
proxyWebsockets = true;
extraConfig = ''
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
# Standard headers for LiveKit
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# --- CORS CONFIGURATION START ---
# 1. Allow all origins (including app.element.io)
add_header 'Access-Control-Allow-Origin' '*' always;
# 2. Allow specific methods (POST is required for /sfu/get)
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
# 3. Allow headers (Content-Type is crucial for JSON)
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
# 4. Handle the OPTIONS preflight request immediately
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# --- CORS CONFIGURATION END ---
'';
};
};
"ntfy.${config.monorepo.vars.remoteHost}" = {
serverName = "ntfy.${config.monorepo.vars.remoteHost}";
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:2586";
proxyWebsockets = true;
};
};
"${config.monorepo.vars.remoteHost}" = {
serverName = "${config.monorepo.vars.remoteHost}";
serverAliases = [ "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" ];
root = "/var/www/${config.monorepo.vars.internetName}-website/";
addSSL = true;
enableACME = true;
};
"list.${config.monorepo.vars.orgHost}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:9090";
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;
'';
};
};
# the port comes from ssh tunnelling
"music.${config.monorepo.vars.remoteHost}" = {
addSSL = true;
enableACME = true;
basicAuthFile = config.sops.secrets."mpd_password".path;
locations."/" = {
proxyPass = "http://localhost:8000";
extraConfig = ''
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_read_timeout 36000s;
'';
};
};
"${config.monorepo.vars.orgHost}" = {
serverName = "${config.monorepo.vars.orgHost}";
root = "/var/www/nullring/";
addSSL = true;
enableACME = true;
};
"mail.${config.monorepo.vars.orgHost}" = {
serverName = "mail.${config.monorepo.vars.orgHost}";
root = "/var/www/dummy";
addSSL = true;
enableACME = true;
};
};
};
}
7.22. Git Web Interface
I enable the git web interface to show off my git repos to the world. This was the easiest frontend to set up on NixOS.
{ lib, config, ... }:
{
services.gitweb = {
gitwebTheme = lib.mkDefault config.monorepo.profiles.server.enable;
projectroot = "/srv/git/";
extraConfig = ''
our $export_ok = "git-daemon-export-ok";
our $site_name = "NullRing Git Server";
our $site_header = "NullRing Projects";
'';
};
}
7.23. Nvidia
I have an Nvidia GPU on my computer.
{ config, lib, pkgs, ... }:
{
hardware = {
graphics.extraPackages = (if config.monorepo.profiles.cuda.enable
then with pkgs; [
libva-vdpau-driver
libvdpau-va-gl
nvidia-vaapi-driver
] else []);
nvidia = {
modesetting.enable = lib.mkDefault config.monorepo.profiles.cuda.enable;
powerManagement = {
enable = lib.mkDefault config.monorepo.profiles.cuda.enable;
finegrained = false;
};
open = config.monorepo.profiles.cuda.enable;
package = config.boot.kernelPackages.nvidiaPackages.stable;
nvidiaSettings = false;
};
};
}
7.24. CUDA
I need CUDA on some computers because I run local LLMs.
{ config, lib, pkgs, ... }:
{
environment.systemPackages = (if config.monorepo.profiles.cuda.enable then with pkgs; [
cudatoolkit
cudaPackages.cudnn
cudaPackages.libcublas
linuxPackages.nvidia_x11
] else []);
}
7.25. Maddy
There is a non declarative part of setting dkims and spf.
{ lib, config, options, ... }:
{
services.maddy = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
openFirewall = true;
hostname = "${config.monorepo.vars.orgHost}";
primaryDomain = "mail.${config.monorepo.vars.orgHost}";
localDomains = [
"$(primary_domain)"
"${config.monorepo.vars.orgHost}"
];
tls = {
loader = "file";
certificates = [
{
keyPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/key.pem";
certPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/fullchain.pem";
}
];
};
config = builtins.replaceStrings [
"imap tcp://0.0.0.0:143"
"submission tcp://0.0.0.0:587"
] [
"imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
"submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
] options.services.maddy.config.default;
ensureCredentials = {
"${config.monorepo.vars.internetName}@${config.monorepo.vars.orgHost}" = {
passwordFile = "/run/secrets/mail_password";
};
"monorepo@${config.monorepo.vars.orgHost}" = {
passwordFile = "/run/secrets/mail_monorepo_password";
};
"discussion@${config.monorepo.vars.orgHost}" = {
passwordFile = "/run/secrets/mail_monorepo_password";
};
};
};
}
7.25.1. Public Inbox
This is my mailing list software that I will use to develop software.
{ lib, config, ... }:
{
systemd.tmpfiles.rules = [
"C+ /var/lib/public-inbox/style.css 0644 public-inbox public-inbox - ${../data/public-inbox.css}"
];
systemd.services.public-inbox-httpd = if config.monorepo.profiles.server.enable then {
preStart = ''
# Copy or link the file.
# Using 'cp' is often safer for sandboxed services than linking to the store. Lol.
cp -f ${../data/public-inbox.css} /var/lib/public-inbox/style.css
chmod 644 /var/lib/public-inbox/style.css
'';
serviceConfig = {
# Allow the service to see the file it just created
BindPaths = [
"/var/lib/public-inbox"
];
ReadOnlyPaths = [ "/var/lib/public-inbox/style.css" ];
# Ensure it can actually write to the directory during preStart
ReadWritePaths = [ "/var/lib/public-inbox" ];
};
} else {};
systemd.services.public-inbox-watch = if config.monorepo.profiles.server.enable then {
after = [ "sops-nix.service" ];
confinement.enable = lib.mkForce false;
preStart = ''
mkdir -p /var/lib/public-inbox/.tmp
chmod 0700 /var/lib/public-inbox/.tmp
ln -sfn ${config.sops.templates."public-inbox-netrc".path} /var/lib/public-inbox/.netrc
'';
environment = {
PUBLIC_INBOX_FORCE_IPV4 = "1";
NETRC = config.sops.templates."public-inbox-netrc".path;
HOME = "/var/lib/public-inbox";
TMPDIR = "/var/lib/public-inbox/.tmp";
};
serviceConfig = {
RestrictSUIDSGID = lib.mkForce false;
ReadWritePaths = [ "/var/lib/public-inbox" ];
RestrictAddressFamilies = lib.mkForce [ "AF_UNIX" "AF_INET" "AF_INET6" ];
PrivateNetwork = lib.mkForce false;
SystemCallFilter = lib.mkForce [];
RootDirectory = lib.mkForce "";
CapabilityBoundingSet = lib.mkForce [ "~" ];
UMask = lib.mkForce "0022";
ProtectSystem = lib.mkForce false;
};
} else {};
services.public-inbox = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
settings = {
publicinbox.css = ["/var/lib/public-inbox/style.css"];
publicinbox.wwwlisting = "all";
};
http = {
enable = true;
port = 9090;
};
inboxes = {
"monorepo" = {
description = "discussion of ret2pop's monorepo project and related work.";
address = [ "monorepo@${config.monorepo.vars.orgHost}" ];
inboxdir = "/var/lib/public-inbox/monorepo";
url = "https://list.${config.monorepo.vars.orgHost}/monorepo";
watch = [ "imaps://monorepo%40${config.monorepo.vars.orgHost}@mail.${config.monorepo.vars.orgHost}/INBOX" ];
};
"discussion" = {
description = "Main Nullring Discussion Mailing List";
address = [ "discussion@${config.monorepo.vars.orgHost}" ];
inboxdir = "/var/lib/public-inbox/discuss";
url = "https://list.${config.monorepo.vars.orgHost}/discussion";
watch = [ "imaps://discussion%40${config.monorepo.vars.orgHost}@mail.${config.monorepo.vars.orgHost}/INBOX" ];
};
};
};
}
7.25.2. Public Inbox CSS
This is a minimal stylesheet for public inbox so that I don't get eye cancer while reading it.
:root {
--bg: #f8f9fa;
--fg: #2e3440;
--link: #5e81ac;
--link-hover: #81a1c1;
--border: #d8dee9;
--card-bg: #ffffff;
--meta-fg: #4c566a; /* Darker gray for better legibility */
--btn-fg: #ffffff;
--max-width: 780px;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1b26;
--fg: #a9b1d6;
--link: #7aa2f7;
--link-hover: #bb9af7;
--border: #414868; /* Distinct border for dark mode */
--card-bg: #1f2335;
--meta-fg: #9aa5ce; /* Brighter gray for dark mode */
--btn-fg: #1a1b26;
}
}
span.q {
color: var(--meta-fg);
font-style: italic;
}
body {
background-color: var(--bg);
color: var(--fg);
line-height: 1.6;
max-width: var(--max-width);
margin: 3rem auto;
padding: 0 1.5rem;
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
/* Keep this for smoother rendering on macOS/iOS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 1. Global Link Fixes */
body a, body a:visited {
color: var(--link);
}
/* 2. Card Styling */
body pre {
white-space: pre-wrap;
background: var(--card-bg);
padding: 2rem;
border-radius: 12px;
border: 1px solid var(--border);
margin-bottom: 2.5rem;
}
/* 3. Header Cleanup */
body pre b:first-of-type {
font-weight: 700;
color: var(--link);
font-size: 1.1rem;
display: block;
margin-bottom: 0.5rem;
}
/* 4. Fixing the "Permalink" label on documentation */
/* We target links ending in / but EXCLUDE system paths like help or mirror */
body pre a[href$="/"]:not([href*="_/"]):not([href*="new.atom"]) {
font-size: 0;
text-decoration: none;
margin-right: 10px;
}
body pre a[href$="/"]:not([href*="_/"]):not([href*="new.atom"]):after {
content: "permalink";
font-size: 11px;
font-weight: bold;
color: var(--fg); /* Use main text color for high contrast */
background: var(--bg);
border: 1px solid var(--link); /* Use link color for the border */
padding: 4px 10px;
border-radius: 4px;
display: inline-block;
}
/* 5. Fixing the "Raw" button contrast */
body pre a[href$="/raw"] {
font-size: 0;
text-decoration: none;
}
body pre a[href$="/raw"]:after {
content: "raw";
font-size: 11px;
font-weight: bold;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--link);
padding: 4px 10px;
border-radius: 4px;
display: inline-block;
}
/* Hover effect for ghost buttons: solid color shift */
body pre a[href$="/"]:hover:after,
body pre a[href$="/raw"]:hover:after {
background: var(--link);
color: var(--btn-fg);
}
/* 6. The Reply Button (Primary Action) */
body pre a[href$="#R"], body pre a[href$="#R"]:visited {
font-size: 0;
text-decoration: none;
}
body pre a[href$="#R"]:after {
content: "REPLY";
font-size: 12px;
font-weight: bold;
padding: 6px 20px;
background: var(--link);
color: var(--btn-fg);
border-radius: 6px;
display: inline-block;
margin-left: 10px;
}
/* 7. Hide clutter */
body pre a[href^="#r"], body pre a[href^="#r"] + b, body hr {
display: none;
}
/* Fix: Mathematically outscore the header rule to keep link text inline */
body pre a[href] b:first-of-type {
display: inline;
font-size: inherit;
margin-bottom: 0;
color: inherit;
}
7.26. Fail2Ban
This is a service that bans bots that try to sign in on my server.
{ lib, config, ... }:
{
services.fail2ban = {
enable = lib.mkDefault config.monorepo.profiles.server.enable;
# Ban IP after 5 failures for 1 hour
maxretry = 5;
bantime = "1h";
banaction = "iptables-allports";
banaction-allports = "iptables-allports";
};
}
7.27. Impermanence
This is my impermanence profile, which removes all files on reboot except for the ones listed below.
{ lib, config, ... }:
{
assertions = [
{
assertion = (! (config.monorepo.profiles.impermanence.enable && (! (config.monorepo.vars.fileSystem == "btrfs"))));
message = "Impermanence requires btrfs filesystem.";
}
];
boot.initrd.postResumeCommands = (if config.monorepo.profiles.impermanence.enable then lib.mkAfter ''
mkdir /btrfs_tmp
mount -t btrfs -n -o subvol=/ /dev/mapper/crypted /btrfs_tmp
if [[ -e /btrfs_tmp/root ]]; then
mkdir -p /btrfs_tmp/old_roots
timestamp=$(date --date="@$(stat -c %Y /btrfs_tmp/root)" "+%Y-%m-%-d_%H:%M:%S")
mv /btrfs_tmp/root "/btrfs_tmp/old_roots/$timestamp"
fi
delete_subvolume_recursively() {
IFS=$'\n'
for i in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
delete_subvolume_recursively "/btrfs_tmp/$i"
done
btrfs subvolume delete "$1"
}
for i in $(find /btrfs_tmp/old_roots/ -maxdepth 1 -mtime +30); do
delete_subvolume_recursively "$i"
done
btrfs subvolume create /btrfs_tmp/root
umount -n /btrfs_tmp
'' else "");
boot.initrd.luks.devices = (if (config.monorepo.vars.fileSystem == "btrfs") then {
crypted = {
device = "/dev/disk/by-partlabel/disk-main-luks";
};
} else {});
fileSystems = if (config.monorepo.profiles.impermanence.enable) then {
"/persistent" = {
neededForBoot = true;
};
"/home" = {
neededForBoot = true;
};
} else {};
environment.persistence."/persistent" = {
enable = config.monorepo.profiles.impermanence.enable;
hideMounts = true;
directories = [
"/var/log"
"/var/lib/bluetooth"
"/var/lib/nixos"
"/var/lib/systemd/coredump"
"/etc/NetworkManager/system-connections"
];
files = [
"/etc/machine-id"
{ file = "/var/keys/secret_file"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
];
users."${config.monorepo.vars.userName}" = {
directories = [
"Downloads"
"music"
"Pictures"
"Documents"
"Videos"
"Monero"
"org"
"monorepo"
"soundfont"
"website_html"
"ardour"
"audacity"
"img"
"email"
"projects"
"secrets"
".emacs.d"
".elfeed"
".electrum"
".mozilla"
".bitmonero"
".config"
".crypto"
{ directory = ".gnupg"; mode = "0700"; }
{ directory = ".ssh"; mode = "0700"; }
{ directory = ".local/share/keyrings"; mode = "0700"; }
".local/share/direnv"
];
files = [
".emacs"
];
};
};
}
7.28. Main Configuration
This is the backbone of the all the NixOS configurations, with all these options being shared because they enhance security.
{ config, pkgs, lib, ... }:
{
imports = [
./public_inbox.nix
./matterbridge.nix
./mautrix.nix
./xserver.nix
./ssh.nix
./pipewire.nix
./tor.nix
./kubo.nix
./nvidia.nix
./cuda.nix
./nginx.nix
./secrets.nix
./git-daemon.nix
./ollama.nix
./i2pd.nix
./gitweb.nix
./conduit.nix
./bitcoin.nix
./murmur.nix
./ngircd.nix
./znc.nix
./docker.nix
./impermanence.nix
./coturn.nix
./maddy.nix
./ntfy-sh.nix
./fail2ban.nix
];
environment.etc."wpa_supplicant.conf".text = ''
country=CA
'';
documentation = {
enable = lib.mkDefault config.monorepo.profiles.documentation.enable;
man.enable = lib.mkDefault config.monorepo.profiles.documentation.enable;
dev.enable = lib.mkDefault config.monorepo.profiles.documentation.enable;
};
environment = {
etc = {
securetty.text = ''
# /etc/securetty: list of terminals on which root is allowed to login.
# See securetty(5) and login(1).
'';
};
};
systemd = {
services.NetworkManager-wait-online.enable = false;
coredump.enable = false;
network.config.networkConfig.IPv6PrivacyExtensions = "kernel";
tmpfiles.settings = {
"restrictetcnixos"."/etc/nixos/*".Z = {
mode = "0000";
user = "root";
group = "root";
};
};
};
boot = {
supportedFilesystems = {
btrfs = true;
ext4 = true;
};
extraModprobeConfig = ''
options snd-usb-audio vid=0x1235 pid=0x8200 device_setup=1
options rtw88_core disable_lps_deep=y power_save=0 disable_aspm_l1ss=y
options rtw88_pci disable_msi=y disable_aspm=y
options rtw_core disable_lps_deep=y
options rtw_pci disable_msi=y disable_aspm=y
options rtw89_core disable_ps_mode=y
options rtw89_pci disable_aspm_l1=y disable_aspm_l1ss=y disable_clkreq=y
options iwlwifi 11n_disable=8 uapsd_disable=1 bt_coex_active=0 disable_11ax=1 power_save=0
'';
extraModulePackages = [ ];
initrd = {
availableKernelModules = [
"xhci_pci"
"ahci"
"usb_storage"
"sd_mod"
"nvme"
"sd_mod"
"ehci_pci"
"rtsx_pci_sdmmc"
"usbhid"
];
kernelModules = [ ];
};
lanzaboote = {
enable = config.monorepo.profiles.secureBoot.enable;
pkiBundle = "/var/lib/sbctl";
};
loader = {
systemd-boot.enable = lib.mkForce ((! config.monorepo.profiles.grub.enable) && (! config.monorepo.profiles.secureBoot.enable));
efi.canTouchEfiVariables = lib.mkForce (! config.monorepo.profiles.grub.enable);
};
kernelModules = [
"snd-seq"
"snd-rawmidi"
"xhci_hcd"
"kvm_intel"
"af_packet"
"ccm"
"ctr"
"cmac"
"arc4"
"ecb"
"michael_mic"
"gcm"
"sha256"
"sha384"
];
kernelParams = [
"cfg80211.reg_alpha2=CA"
"usbcore.autosuspend=-1"
"pcie_aspm=off"
"pci=noaer"
# "debugfs=off"
"page_alloc.shuffle=1"
"slab_nomerge"
# "page_poison=1"
# madaidan
"pti=on"
"randomize_kstack_offset=on"
"vsyscall=none"
# "lockdown=confidentiality"
# cpu
"spectre_v2=on"
"spec_store_bypass_disable=on"
"tsx=off"
"l1tf=full,force"
"kvm.nx_huge_pages=force"
# hardened
"extra_latent_entropy"
# mineral
# "init_on_alloc=1"
# "random.trust_bootloader=off"
# "init_on_free=1"
"quiet"
# "loglevel=0"
];
blacklistedKernelModules = [
"netrom"
"rose"
"adfs"
"affs"
"bfs"
"befs"
"cramfs"
"efs"
"erofs"
"exofs"
"freevxfs"
"f2fs"
"hfs"
"hpfs"
"jfs"
"minix"
"nilfs2"
"ntfs"
"omfs"
"qnx4"
"qnx6"
"sysv"
"ufs"
];
kernel.sysctl = {
"kernel.ftrace_enabled" = false;
"net.core.bpf_jit_enable" = false;
"kernel.kptr_restrict" = 2;
# madaidan
"kernel.smtcontrol" = "on";
"vm.swappiness" = 1;
"vm.unprivileged_userfaultfd" = 0;
"dev.tty.ldisc_autoload" = 0;
"kernel.kexec_load_disabled" = 1;
"kernel.sysrq" = 4;
"kernel.perf_event_paranoid" = 3;
# net
"net.ipv4.ip_forward" = 1;
"net.ipv4.icmp_echo_ignore_broadcasts" = true;
# "net.ipv4.conf.all.accept_redirects" = false;
# "net.ipv4.conf.all.secure_redirects" = false;
# "net.ipv4.conf.default.accept_redirects" = false;
# "net.ipv4.conf.default.secure_redirects" = false;
# "net.ipv6.conf.all.accept_redirects" = false;
# "net.ipv6.conf.default.accept_redirects" = false;
};
};
networking = {
nameservers = [ "8.8.8.8" "1.1.1.1"];
dhcpcd.enable = (! config.monorepo.profiles.server.enable);
networkmanager = {
enable = true;
wifi = {
powersave = false;
};
ensureProfiles = {
profiles = {
home-wifi = {
connection = {
id = "TELUS6572";
permissions = "";
type = "wifi";
};
ipv4 = {
dns-search = "";
method = "auto";
};
ipv6 = {
addr-gen-mode = "stable-privacy";
dns-search = "";
method = "auto";
};
wifi = {
mac-address-blacklist = "";
mode = "infrastructure";
ssid = "TELUS6572";
};
wifi-security = {
auth-alg = "open";
key-mgmt = "wpa-psk";
# when someone actually steals my internet then I will be concerned.
# This password only matters if you actually show up to my house in real life.
# That would perhaps allow for some nasty networking related shenanigans.
# I guess we'll cross that bridge when I get there.
psk = "b4xnrv6cG6GX";
};
};
};
};
};
firewall = {
allowedTCPPorts = [ 22 11434 ];
allowedUDPPorts = [ ];
};
};
hardware = {
wirelessRegulatoryDatabase = true;
enableAllFirmware = true;
cpu.intel.updateMicrocode = true;
graphics.enable = ! config.monorepo.profiles.ttyonly.enable;
bluetooth = {
enable = lib.mkDefault (! config.monorepo.profiles.ttyonly.enable);
powerOnBoot = lib.mkDefault (! config.monorepo.profiles.ttyonly.enable);
};
};
services = {
pulseaudio.enable = ! config.monorepo.profiles.pipewire.enable;
chrony = {
enable = true;
enableNTS = true;
servers = [ "time.cloudflare.com" "ptbtime1.ptb.de" "ptbtime2.ptb.de" ];
};
jitterentropy-rngd.enable = true;
resolved.settings.Resolve.DNSSEC = true;
# usbguard.enable = true;
usbguard.enable = false;
dbus.apparmor = "enabled";
kanata.enable = true;
# Misc.
udev = {
extraRules = '''';
packages = if config.monorepo.profiles.workstation.enable then with pkgs; [
platformio-core
platformio-core.udev
openocd
] else [];
};
printing.enable = lib.mkDefault config.monorepo.profiles.workstation.enable;
udisks2.enable = (! config.monorepo.profiles.ttyonly.enable);
};
programs = {
nix-ld.enable = true;
zsh.enable = true;
light.enable = true;
ssh.enableAskPassword = false;
};
nixpkgs = {
hostPlatform = lib.mkDefault "x86_64-linux";
config = {
allowUnfree = true;
cudaSupport = lib.mkDefault config.monorepo.profiles.cuda.enable;
};
};
security = {
acme = {
acceptTerms = true;
defaults.email = "ret2pop@gmail.com";
};
apparmor = {
enable = true;
killUnconfinedConfinables = true;
packages = with pkgs; [
apparmor-profiles
];
policies = {
firefox.path = "${pkgs.apparmor-profiles}/share/apparmor/extra-profiles/firefox";
};
};
pam.loginLimits = [
{ domain = "*"; item = "nofile"; type = "-"; value = "32768"; }
{ domain = "*"; item = "memlock"; type = "-"; value = "32768"; }
];
rtkit.enable = true;
lockKernelModules = true;
protectKernelImage = true;
allowSimultaneousMultithreading = true;
forcePageTableIsolation = true;
tpm2 = {
enable = true;
pkcs11.enable = true;
tctiEnvironment.enable = true;
};
auditd.enable = true;
audit.enable = true;
chromiumSuidSandbox.enable = (! config.monorepo.profiles.ttyonly.enable);
sudo.enable = true;
};
xdg.portal = {
enable = (! config.monorepo.profiles.ttyonly.enable);
wlr.enable = (! config.monorepo.profiles.ttyonly.enable);
extraPortals = with pkgs; if (! config.monorepo.profiles.ttyonly.enable) then [
xdg-desktop-portal-gtk
xdg-desktop-portal
xdg-desktop-portal-hyprland
] else [];
config.common.default = "*";
};
environment.etc."gitconfig".text = ''
[init]
defaultBranch = main
'';
environment.extraInit = ''
umask 0022
'';
environment.systemPackages = with pkgs; [
restic
sbctl
git
git-lfs
git-lfs-transfer
vim
curl
nmap
exiftool
(writeShellScriptBin "new-repo"
''
#!/bin/bash
cd /srv/git
git init --bare "$1"
vim "$1/description"
chown -R git:git "$1"
''
)
];
users.groups.nginx = lib.mkDefault {};
users.groups.git = lib.mkDefault {};
users.groups.ircd = lib.mkDefault {};
users.groups.ngircd = lib.mkDefault {};
users.groups.conduit = lib.mkDefault {};
users.groups.livekit = lib.mkDefault {};
users.groups.matterbridge = lib.mkDefault {};
users.groups.maddy = lib.mkDefault {};
users.groups.ntfy-sh = lib.mkDefault {};
users.groups.public-inbox = lib.mkDefault {};
users.users = {
conduit = {
isSystemUser = lib.mkDefault true;
group = "conduit";
};
matterbridge = {
isSystemUser = lib.mkDefault true;
group = "matterbridge";
};
maddy = {
isSystemUser = lib.mkDefault true;
group = "maddy";
extraGroups = [ "acme" "nginx" ];
};
ntfy-sh = {
isSystemUser = lib.mkDefault true;
group = "ntfy-sh";
extraGroups = [ "acme" "nginx" ];
};
public-inbox = {
isSystemUser = lib.mkDefault true;
group = "public-inbox";
extraGroups = [ "acme" "nginx" ];
};
ngircd = {
isSystemUser = lib.mkDefault true;
group = "ngircd";
extraGroups = [ "acme" "nginx" ];
};
livekit = {
isSystemUser = lib.mkDefault true;
group = "livekit";
extraGroups = [ "acme" "nginx" ];
};
ircd = {
isSystemUser = lib.mkDefault true;
group = "ircd";
home = "/home/ircd";
};
nginx = {
group = "nginx";
isSystemUser = lib.mkDefault true;
extraGroups = [
"acme"
];
};
root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell"
];
git = {
isSystemUser = true;
home = "/srv/git";
shell = "/bin/sh";
group = "git";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell"
];
};
"${config.monorepo.vars.userName}" = {
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell"
];
linger = true;
initialPassword = "${config.monorepo.vars.userName}";
isNormalUser = true;
description = config.monorepo.vars.fullName;
extraGroups = [ "networkmanager" "wheel" "video" "docker" "jackaudio" "tss" "dialout" "docker" ];
shell = pkgs.zsh;
packages = [];
};
};
nixpkgs.config.permittedInsecurePackages = [
"python3.13-ecdsa-0.19.1"
"olm-3.2.16"
];
nix = {
settings = {
keep-outputs = true;
keep-derivations = true;
auto-optimise-store = true;
max-jobs = 4;
cores = 0;
substituters = [
"https://cache.nixos-cuda.org"
];
trusted-public-keys = [
"cache.nixos-cuda.org:74DUi4Ye579gUqzH4ziL9IyiJBlDpMRn9MBN8oNan9M="
];
experimental-features = "nix-command flakes ca-derivations";
trusted-users = [ "@wheel" ];
};
gc.automatic = true;
};
time.timeZone = config.monorepo.vars.timeZone;
i18n.defaultLocale = "en_CA.UTF-8";
system.stateVersion = "24.11";
}
7.29. Disko
This is the disko configuration for my continuity system. It features a boot and ext4 partition, with configurable disk.
7.29.1. Btrfs
{ lib, config, ... }:
let
spec = {
disko.devices = {
disk = {
main = {
type = "disk";
device = config.monorepo.vars.device;
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted";
passwordFile = "/tmp/secret.key";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
subvolumes = {
"/root" = {
mountpoint = "/";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"/home" = {
mountpoint = "/home";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"/nix" = {
mountpoint = "/nix";
mountOptions = [
"compress=zstd"
"noatime"
];
};
"/persistent" = {
mountpoint = "/persistent";
mountOptions = [
"compress=zstd"
"noatime"
];
};
};
};
};
};
};
};
};
};
};
};
in
{
monorepo.vars.diskoSpec = spec;
disko.devices = spec.disko.devices;
}
7.29.2. Simple
This configuration is used for simple partitioning schemes with EFI.
{ lib, config, ... }:
let
spec = {
disko.devices = {
disk = {
my-disk = {
device = config.monorepo.vars.device;
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
type = "EF00";
size = "500M";
priority = 1;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
priority = 2;
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
};
in
{
monorepo.vars.diskoSpec = spec;
disko.devices = spec.disko.devices;
}
7.29.3. BIOS
For machines that use BIOS instead of EFI.
{ config, lib, ... }:
let
spec = {
disko.devices = {
disk = {
main = {
device = config.monorepo.vars.device;
type = "disk";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02";
};
root = {
label = "disk-main-root";
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
};
in
{
monorepo.vars.diskoSpec = spec;
disko.devices = spec.disko.devices;
}
7.30. Home
7.30.1. Default Home Profile
As you can see, I have my installed home packages installed based on the profiles enabled. Also, I have many imports that we'll go through next.
{ lib, config, pkgs, sops-nix, ... }:
{
imports = [
sops-nix.homeManagerModules.sops
../vars.nix
./fcitx.nix
./emacs.nix
./firefox.nix
./git.nix
./hyprland.nix
./mpv.nix
./yt-dlp.nix
./wofi.nix
./kitty.nix
./waybar.nix
./zsh.nix
./mbsync.nix
./msmtp.nix
./gammastep.nix
./mpd.nix
./mako.nix
./user.nix
./gtk.nix
./secrets.nix
];
options = {
monorepo.profiles = {
enable = lib.mkEnableOption "Enables home manager desktop configuration";
# Programs
graphics.enable = lib.mkEnableOption "Enables graphical programs for user";
lang-c.enable = lib.mkEnableOption "Enables C language support";
lang-sh.enable = lib.mkEnableOption "Enables sh language support";
lang-rust.enable = lib.mkEnableOption "Enables Rust language support";
lang-python.enable = lib.mkEnableOption "Enables python language support";
lang-sol.enable = lib.mkEnableOption "Enables solidity language support";
lang-openscad.enable = lib.mkEnableOption "Enables openscad language support";
lang-js.enable = lib.mkEnableOption "Enables javascript language support";
lang-nix.enable = lib.mkEnableOption "Enables nix language support";
lang-idris.enable = lib.mkEnableOption "Enables idris language support";
lang-agda.enable = lib.mkEnableOption "Enables agda language support";
lang-coq.enable = lib.mkEnableOption "Enables coq language support";
lang-lean.enable = lib.mkEnableOption "Enables lean language support";
lang-haskell.enable = lib.mkEnableOption "Enables haskell language support";
crypto.enable = lib.mkEnableOption "Enables various cryptocurrency wallets";
art.enable = lib.mkEnableOption "Enables various art programs";
music.enable = lib.mkEnableOption "Enables mpd";
workstation.enable = lib.mkEnableOption "Enables workstation packages (music production and others)";
cuda.enable = lib.mkEnableOption "Enables CUDA user package builds";
hyprland.enable = lib.mkEnableOption "Enables hyprland";
email = {
email = lib.mkOption {
type = lib.types.str;
default = "ret2pop@nullring.xyz";
example = "john@example.com";
description = "Email address and imaps/smtps account";
};
imapsServer = lib.mkOption {
type = lib.types.str;
default = "mail.nullring.xyz";
example = "imap.example.com";
description = "imaps server address";
};
smtpsServer = lib.mkOption {
type = lib.types.str;
default = "mail.nullring.xyz";
example = "smtp.example.com";
description = "smtp server address";
};
enable = lib.mkEnableOption "Enables email";
};
};
};
config = {
home.packages = (if config.monorepo.profiles.email.enable then [ pkgs.mu ] else [])
++
(if config.monorepo.profiles.lang-c.enable then (with pkgs; [
autobuild
clang
gdb
gnumake
bear
clang-tools
autotools-language-server
]) else [])
++
(if config.monorepo.profiles.workstation.enable then (with pkgs; [
mumble
]) else [])
++
(if config.monorepo.profiles.lang-js.enable then (with pkgs; [
nodejs
bun
yarn
typescript
typescript-language-server
vscode-langservers-extracted
]) else [])
++
(if config.monorepo.profiles.lang-rust.enable then (with pkgs; [
cargo
rust-analyzer
rustfmt
]) else [])
++
(if config.monorepo.profiles.lang-python.enable then (with pkgs; [
poetry
python3
python314Packages.python-lsp-server
]) else [])
++
(if config.monorepo.profiles.lang-sol.enable then (with pkgs; [
solc
]) else [])
++
(if config.monorepo.profiles.lang-openscad.enable then (with pkgs; [
openscad
openscad-lsp
]) else [])
++
(if config.monorepo.profiles.lang-sh.enable then (with pkgs; [
bash-language-server
]) else [])
++
(if config.monorepo.profiles.lang-haskell.enable then (with pkgs; [
haskell-language-server
haskellPackages.hlint
ghc
]) else [])
++
(if config.monorepo.profiles.lang-coq.enable then (with pkgs; [
coq
]) else [])
++
(if config.monorepo.profiles.lang-lean.enable then (with pkgs; [
lean4
]) else [])
++
(if config.monorepo.profiles.lang-agda.enable then (with pkgs; [
agda
]) else [])
++
(if config.monorepo.profiles.lang-idris.enable then (with pkgs; [
idris
idris2Packages.idris2Lsp
]) else [])
++
(if config.monorepo.profiles.lang-nix.enable then (with pkgs; [
nil
nixd
nixfmt
nix-prefetch-scripts
]) else [])
++
(if config.monorepo.profiles.crypto.enable then (with pkgs; [
bitcoin
# electrum
monero-cli
monero-gui
]) else [])
++
(if config.monorepo.profiles.art.enable then (with pkgs; [
inkscape
# krita
]) else [])
++
(if config.monorepo.profiles.music.enable then (with pkgs; [
mpc
sox
]) else [])
++
(if config.monorepo.profiles.workstation.enable then (with pkgs; [
alsa-utils
alsa-scarlett-gui
ardour
audacity
# blender
foxdot
fluidsynth
qjackctl
qsynth
qpwgraph
imagemagick
supercollider
inkscape
# kdePackages.kdenlive
# kicad
murmur
]) else []);
monorepo.profiles = {
enable = lib.mkDefault true;
music.enable = lib.mkDefault config.monorepo.profiles.enable;
hyprland.enable = lib.mkDefault config.monorepo.profiles.enable;
email.enable = lib.mkDefault config.monorepo.profiles.enable;
# Programming
graphics.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-c.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-rust.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-python.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-sol.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-sh.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-openscad.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-js.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-nix.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-coq.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-lean.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-haskell.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-idris.enable = lib.mkDefault config.monorepo.profiles.enable;
lang-agda.enable = lib.mkDefault config.monorepo.profiles.enable;
crypto.enable = lib.mkDefault config.monorepo.profiles.enable;
art.enable = lib.mkDefault config.monorepo.profiles.enable;
workstation.enable = lib.mkDefault config.monorepo.profiles.enable;
};
};
}
7.30.2. Secrets
{ config, ... }:
{
sops = {
defaultSopsFile = if config.monorepo.profiles.graphics.enable
then ../../secrets/secrets.yaml
else ../../secrets/vps_secrets.yaml;
age = {
keyFile = "/home/${config.monorepo.vars.userName}/.config/sops/age/keys.txt";
};
secrets = if config.monorepo.profiles.graphics.enable then {
mail = {
format = "yaml";
path = "${config.sops.defaultSymlinkPath}/mail";
};
cloudflare-dns = {
format = "yaml";
path = "${config.sops.defaultSymlinkPath}/cloudflare-dns";
};
digikey = {
format = "yaml";
path = "${config.sops.defaultSymlinkPath}/digikey";
};
dn42 = {
format = "yaml";
path = "${config.sops.defaultSymlinkPath}/dn42";
};
} else {
};
defaultSymlinkPath = "/run/user/1000/secrets";
defaultSecretsMountPoint = "/run/user/1000/secrets.d";
};
}
7.30.3. Firefox
I conditionally enable metamask based on the cryptocurrency option. Everything else here should be straightforward.
{ lib, config, pkgs, ... }:
{
programs.firefox = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
package = pkgs.firefox;
policies = {
EnableTrackingProtection = true;
OfferToSaveLogins = false;
};
profiles = {
default = {
id = 0;
name = "default";
isDefault = true;
extensions.packages = with pkgs.nur.repos.rycee.firefox-addons; [
ublock-origin
tree-style-tab
firefox-color
vimium
]
++ (lib.optional
config.monorepo.profiles.crypto.enable pkgs.nur.repos.rycee.firefox-addons.metamask);
settings = {
media = {
memory_cache_max_size = 65536;
cache_readahead_limit = 7200;
cache_resume_threshold = 3600;
peerconnection.ice = {
proxy_only_if_behind_proxy = true;
default_address_only = true;
};
};
gfx = {
content.skia-font-cache-size = 20;
canvas.accelerated = {
cache-items = 4096;
cache-size = 512;
};
};
network = {
http = {
max-connections = 1800;
max-persistent-connections-per-server = 10;
max-urgent-start-excessive-connections-per-host = 5;
referer.XOriginTrimmingPolicy = 2;
};
buffer.cache = {
size = 262144;
count = 128;
};
dns = {
max_high_priority_threads = 8;
disablePrefetch = true;
};
pacing.requests.enabled = false;
dnsCacheExpiration = 3600;
ssl_tokens_cache_capacity = 10240;
prefetch-next = false;
predictor.enabled = false;
cookie.sameSite.noneRequiresSecure = true;
IDN_show_punycode = true;
auth.subresource-http-auth-allow = 1;
captive-portal-service.enabled = false;
connectivity-service.enabled = false;
};
browser = {
download = {
always_ask_before_handling_new_types = true;
manager.addToRecentDocs = false;
open_pdf_attachments_inline = true;
start_downloads_in_tmp_dir = true;
};
urlbar = {
suggest.quicksuggest.sponsored = false;
suggest.quicksuggest.nonsponsored = false;
suggest.calculator = true;
update2.engineAliasRefresh = true;
unitConversion.enabled = true;
trending.featureGate = false;
};
search = {
separatePrivateDefault.ui.enabled = true;
suggest.enabled = false;
};
newtabpage.activity-stream = {
feeds = {
topsites = false;
section.topstories = false;
telemetry = false;
};
asrouter.userprefs.cfr = {
addons = false;
features = false;
};
telemetry = false;
};
privatebrowsing = {
vpnpromourl = "";
forceMediaMemoryCache = true;
};
display = {
focus_ring_on_anything = true;
focus_ring_style = 0;
focus_ring_width = 0;
};
cache.jsbc_compression_level = 3;
helperApps.deleteTempFileOnExit = true;
uitour.enabled = false;
sessionstore.interval = 60000;
formfill.enable = false;
xul.error_pages.expert_bad_cert = true;
contentblocking.category = "strict";
ping-centre.telemetry = false;
discovery.enabled = false;
shell.checkDefaultBrowser = false;
preferences.moreFromMozilla = false;
tabs.tabmanager.enabled = false;
aboutConfig.showWarning = false;
aboutwelcome.enabled = false;
bookmarks.openInTabClosesMenu = false;
menu.showViewImageInfo = true;
compactmode.show = true;
safebrowsing.downloads.remote.enabled = false;
tabs.crashReporting.sendReport = false;
crashReports.unsubmittedCheck.autoSubmit2 = false;
privateWindowSeparation.enabled = false;
};
security = {
mixed_content = {
block_display_content = true;
upgrade_display_content = true;
};
insecure_connection_text = {
enabled = true;
pbmode.enabled = true;
};
OCSP.enabled = 0;
remote_settings.crlite_filters.enabled = true;
pki.crlite_mode = 2;
ssl.treat_unsafe_negotiation_as_broken = true;
tls.enable_0rtt_data = false;
};
toolkit = {
telemetry = {
unified = false;
enabled = false;
server = "data:,";
archive.enabled = false;
newProfilePing.enabled = false;
shutdownPingSender.enabled = false;
updatePing.enabled = false;
bhrPing.enabled = false;
firstShutdownPing.enabled = false;
coverage.opt-out = true;
};
coverage = {
opt-out = true;
endpoint.base = "";
};
legacyUserProfileCustomizations.stylesheets = true;
};
dom = {
security = {
https_first = true;
https_first_schemeless = true;
sanitizer.enabled = true;
};
enable_web_task_scheduling = true;
};
layout = {
css = {
grid-template-masonry-value.enabled = true;
has-selector.enabled = true;
prefers-color-scheme.content-override = 2;
};
word_select.eat_space_to_next_word = false;
};
urlclassifier = {
trackingSkipURLs = "*.reddit.com, *.twitter.com, *.twimg.com, *.tiktok.com";
features.socialtracking.skipURLs = "*.instagram.com, *.twitter.com, *.twimg.com";
};
privacy = {
globalprivacycontrol.enabled = true;
history.custom = true;
userContext.ui.enabled = true;
trackingprotection = {
enabled = true;
pbmode.enabled = true;
socialtracking.enabled = true;
};
};
full-screen-api = {
transition-duration = {
enter = "0 0";
leave = "0 0";
};
warning = {
delay = -1;
timeout = 0;
};
};
permissions.default = {
desktop-notification = 2;
geo = 2;
};
signon = {
formlessCapture.enabled = false;
privateBrowsingCapture.enabled = false;
};
datareporting = {
policy.dataSubmissionEnabled = false;
healthreport.uploadEnabled = false;
};
extensions = {
pocket.enabled = false;
getAddons.showPane = false;
htmlaboutaddons.recommendations.enabled = false;
postDownloadThirdPartyPrompt = false;
};
app = {
shield.optoutstudies.enabled = false;
normandy.enabled = false;
normandy.api_url = "";
};
image.mem.decode_bytes_at_a_time = 32768;
editor.truncate_user_pastes = false;
pdfjs.enableScripting = false;
geo.provider.network.url = "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%";
permissions.manager.defaultsUrl = "";
webchannel.allowObject.urlWhitelist = "";
breakpad.reportURL = "";
captivedetect.canonicalURL = "";
cookiebanners.service.mode = 1;
findbar.highlightAll = true;
content.notify.interval = 100000;
};
};
};
};
}
7.30.4. Fcitx
This is a virtual keyboard program for writing in multiple languages. I use this sometimes.
{ config, pkgs, lib, ... }:
{
i18n.inputMethod = {
type = "fcitx5";
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
fcitx5.addons = if config.monorepo.profiles.graphics.enable then (with pkgs; [
fcitx5-gtk
qt6Packages.fcitx5-chinese-addons
qt6Packages.fcitx5-configtool
fcitx5-mozc
fcitx5-rime
]) else [];
};
}
Note that I configure fcitx with chinese and some japanese input enabled.
7.30.5. Emacs
I install all my emacs packages within Nix so that they build deterministically with native
compilation, and because I can fetch their exact versions. Note that I have a stub
configuration here that tells emacs to load my real configuration at ~/monorepo/config/emacs.org
as an org file which gets automatically tangled to an emacs-lisp file.
{ lib, config, pkgs, ... }:
{
programs.emacs =
{
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
package = pkgs.emacs-pgtk;
extraConfig = ''
(setq debug-on-error t)
(setq system-email "${config.monorepo.profiles.email.email}")
(setq system-username "${config.monorepo.vars.internetName}")
(setq system-fullname "${config.monorepo.vars.fullName}")
(setq system-gpgkey "${config.monorepo.vars.gpgKey}")
(load "${pkgs.writeText "init.el" (builtins.readFile ../../init.el)}")
'';
extraPackages = epkgs: [
epkgs.agda2-mode
epkgs.all-the-icons
epkgs.auctex
epkgs.catppuccin-theme
epkgs.company
epkgs.company-solidity
epkgs.counsel
epkgs.centaur-tabs
epkgs.dashboard
epkgs.doom-themes
epkgs.doom-modeline
epkgs.elfeed
epkgs.elfeed-org
epkgs.elfeed-tube
epkgs.elfeed-tube-mpv
epkgs.elpher
epkgs.ement
epkgs.emmet-mode
epkgs.emms
epkgs.enwc
epkgs.evil
epkgs.evil-collection
epkgs.evil-commentary
epkgs.evil-org
epkgs.f
epkgs.flycheck
epkgs.general
epkgs.gptel
epkgs.gruvbox-theme
epkgs.haskell-mode
epkgs.htmlize
epkgs.idris-mode
epkgs.irony-eldoc
epkgs.ivy
epkgs.ivy-pass
epkgs.kiwix
epkgs.latex-preview-pane
epkgs.lsp-ivy
epkgs.lsp-mode
epkgs.lsp-haskell
epkgs.lyrics-fetcher
epkgs.mastodon
epkgs.magit
epkgs.magit-delta
epkgs.mu4e
epkgs.minuet
epkgs.nix-mode
epkgs.org-fragtog
epkgs.org-journal
epkgs.org-roam
epkgs.org-roam-ui
epkgs.org-superstar
epkgs.page-break-lines
epkgs.password-store
epkgs.pdf-tools
epkgs.pinentry
epkgs.platformio-mode
epkgs.projectile
epkgs.rustic
epkgs.scad-mode
epkgs.simple-httpd
epkgs.solidity-flycheck
epkgs.solidity-mode
epkgs.sudo-edit
epkgs.telega
epkgs.treemacs
epkgs.treemacs-evil
epkgs.treemacs-magit
epkgs.treemacs-projectile
epkgs.treesit-auto
epkgs.typescript-mode
epkgs.unicode-fonts
epkgs.use-package
epkgs.vterm
epkgs.wgrep
epkgs.web-mode
epkgs.websocket
epkgs.which-key
epkgs.writegood-mode
epkgs.writeroom-mode
epkgs.yaml-mode
epkgs.yasnippet
epkgs.yasnippet-snippets
];
};
}
7.30.6. Gammastep
This is a program like redshift for making your screen emit more red and less blue light. Here I have the long and lat set for Vancouver, but you should replace it if you live outside the timezone.
{ lib, config, ... }:
{
services.gammastep = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
provider = "manual";
latitude = 49.282730;
longitude = -123.120735;
temperature = {
day = 5000;
night = 3000;
};
settings = {
general = {
adjustment-method = "wayland";
};
};
};
}
7.30.7. Git
My git configuration uses information set in the vars.nix in order to set configuration options.
Make sure those are set correctly. I've set it to sign by default.
{ lib, config, ... }:
{
programs.git = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
lfs.enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
userName = config.monorepo.vars.fullName;
userEmail = config.monorepo.profiles.email.email;
signing = {
key = config.monorepo.vars.gpgKey;
signByDefault = true;
};
extraConfig = {
init.defaultBranch = "main";
};
aliases = {
pl = "pull";
ps = "push";
co = "checkout";
c = "commit";
a = "add";
st = "status";
sw = "switch";
b = "branch";
};
};
}
7.30.8. Hyprland
My compositor/window manager. This automatically starts on startup. Instructions on how to use this component will come soon.
{ lib, config, wallpapers, pkgs, scripts, ... }:
{
wayland.windowManager.hyprland = {
enable = lib.mkDefault config.monorepo.profiles.hyprland.enable;
package = pkgs.hyprland;
xwayland.enable = true;
systemd.enable = true;
settings = {
"$mod" = "SUPER";
bezier = [
"overshot, 0.05, 0.9, 0.1, 1.05"
];
animation = [
# "workspaces, 1, 10, overshot"
"windows, 1, 2, default"
"workspaces, 1, 2, default, slidefade 20%"
];
exec-once = [
"waybar"
"swww-daemon --format xrgb"
"sh -c 'swww img \"$(find ${wallpapers} -type f \\( -iname \"*.jpg\" -o -iname \"*.png\" \\) | shuf -n1)\"'"
"fcitx5-remote -r"
"fcitx5 -d --replace"
"fcitx5-remote -r"
"emacs"
"firefox"
];
env = [
"LIBVA_DRIVER_NAME,nvidia"
"XDG_SESSION_TYPE,wayland"
"GBM_BACKEND,nvidia-drm"
"__GLX_VENDOR_LIBRARY_NAME,nvidia"
"ELECTRON_OZONE_PLATFORM_HINT,auto"
];
monitor = [
"DP-4,2560x1440@165.000000,0x0,1"
"Unknown-1,disable"
];
layerrule = [
{
name = "waybar blur";
"match:namespace" = "waybar";
blur = "on";
}
];
windowrule = [
{
name = "emacs";
"match:class" = "emacs";
workspace = 1;
}
{
name = "firefox";
"match:class" = "firefox";
workspace = 2;
}
{
name = "vesktop";
"match:class" = "vesktop";
workspace = 3;
}
{
name = "pavucontrol";
"match:class" = "pavucontrol";
workspace = 4;
}
{
name = "qpwgraph";
"match:class" = "qpwgraph";
workspace = 4;
}
{
name = "mpv";
"match:class" = "mpv";
workspace = 4;
}
];
bind = [
"$mod, F, exec, firefox"
"$mod, Return, exec, kitty"
"$mod, E, exec, emacs"
"$mod, B, exec, bitcoin-qt"
"$mod, S, exec, pavucontrol"
"$mod, M, exec, monero-wallet-gui"
"$mod, V, exec, vesktop"
"$mod, C, exec, fluffychat"
"$mod, D, exec, wofi --show run"
"$mod, P, exec, bash ${scripts}/powermenu.sh"
"$mod, Q, killactive"
"$mod SHIFT, H, movewindow, l"
"$mod SHIFT, L, movewindow, r"
"$mod SHIFT, K, movewindow, u"
"$mod SHIFT, J, movewindow, d"
"$mod SHIFT, T, togglefloating"
"$mod SHIFT, F, fullscreen"
"$mod, H, movefocus, l"
"$mod, L, movefocus, r"
"$mod, K, movefocus, u"
"$mod, J, movefocus, d"
", XF86AudioPlay, exec, mpc toggle"
", Print, exec, grim"
"$mod, right, resizeactive, 30 0"
"$mod, left, resizeactive, -30 0"
"$mod, up, resizeactive, 0 -30"
"$mod, down, resizeactive, 0 30"
]
++ (
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"
"$mod, mouse:273, resizewindow"
"$mod ALT, mouse:272, resizewindow"
];
binde = [
", XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+"
", XF86AudioLowerVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%-"
", XF86AudioNext, exec, mpc next"
", XF86AudioPrev, exec, mpc prev"
", XF86MonBrightnessUp , exec, xbacklight -inc 10"
", XF86MonBrightnessDown, exec, xbacklight -dec 10"
];
decoration = {
blur = {
enabled = true;
size = 9;
passes = 4;
contrast = 0.8;
brightness = 1.1;
noise = 0.02;
new_optimizations = true;
ignore_opacity = true;
xray = false;
};
rounding = 5;
};
input = {
scroll_method = "on_button_down";
scroll_button = 276;
sensitivity = -0.5;
kb_options = "caps:swapescape";
repeat_delay = 300;
repeat_rate = 50;
natural_scroll = false;
touchpad = {
natural_scroll = true;
disable_while_typing = true;
tap-to-click = true;
};
};
cursor = {
no_hardware_cursors = true;
};
misc = {
force_default_wallpaper = 0;
disable_hyprland_logo = true;
};
};
};
}
7.30.9. Kitty
I've set my terminal, kitty, to use catppuccin colors.
{ lib, config, ... }:
{
programs.kitty = {
enable = lib.mkDefault (config.monorepo.profiles.hyprland.enable && config.monorepo.profiles.graphics.enable);
settings = {
enable_audio_bell = false;
font_family = "Iosevka Nerd Font";
font_size = 14;
confirm_os_window_close = 0;
background_opacity = "0.7";
# Catppuccin theme
foreground = "#cdd6f4";
background = "#1e1e2e";
selection_foreground = "#1e1e2e";
selection_background = "#f5e0dc";
cursor = "#f5e0dc";
cursor_text_color = "#1e1e2e";
url_color = "#f5e0dc";
active_border_color = "#B4BEFE";
inactive_border_color = "#6C7086";
bell_border_color = "#F9E2AF";
wayland_titlebar_color = "#1E1E2E";
macos_titlebar_color = "#1E1E2E";
active_tab_foreground = "#11111B";
active_tab_background = "#CBA6F7";
inactive_tab_foreground = "#CDD6F4";
inactive_tab_background = "#181825";
tab_bar_background = "#11111B";
mark1_foreground = "#1E1E2E";
mark1_background = "#B4BEFE";
mark2_foreground = "#1E1E2E";
mark2_background = "#CBA6F7";
mark3_foreground = "#1E1E2E";
mark3_background = "#74C7EC";
color0 = "#45475A";
color8 = "#585B70";
color1 = "#F38BA8";
color9 = "#F38BA8";
color2 = "#A6E3A1";
color10 = "#A6E3A1";
color3 = "#F9E2AF";
color11 = "#F9E2AF";
color4 = "#89B4FA";
color12 = "#89B4FA";
color5 = "#F5C2E7";
color13 = "#F5C2E7";
color6 = "#94E2D5";
color14 = "#94E2D5";
color7 = "#BAC2DE";
color15 = "#A6ADC8";
};
};
}
7.30.10. Mako
This is my notification system. My flake automatically fetches the notification sound, so you are all set from the get-go!
{ lib, config, sounds, ... }:
{
services.mako = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
settings = {
on-notify = "exec mpv ${sounds}/polite.ogg --no-config --no-video";
background-color = "#11111bf8";
text-color = "#cdd6f4";
border-color = "#89b4faff";
border-radius = 1;
font = "Fira Code 10";
default-timeout = 3000;
};
};
}
7.30.11. Mbsync
Note that in order to use my email configuration, your imaps and smtps servers must be
encrypted. This module uses the vars.nix as well as the home default.nix options.
{ lib, config, ... }:
{
programs.mbsync = {
enable = lib.mkDefault config.monorepo.profiles.email.enable;
extraConfig = ''
IMAPAccount ${config.monorepo.vars.internetName}
Host ${config.monorepo.profiles.email.imapsServer}
User ${config.monorepo.profiles.email.email}
PassCmd "cat ${config.sops.secrets.mail.path}"
Port 993
TLSType IMAPS
AuthMechs *
CertificateFile /etc/ssl/certs/ca-certificates.crt
IMAPStore ${config.monorepo.vars.internetName}-remote
Account ${config.monorepo.vars.internetName}
MaildirStore ${config.monorepo.vars.internetName}-local
Path ~/email/${config.monorepo.vars.internetName}/
Inbox ~/email/${config.monorepo.vars.internetName}/INBOX
SubFolders Verbatim
Channel ${config.monorepo.vars.internetName}
Far :${config.monorepo.vars.internetName}-remote:
Near :${config.monorepo.vars.internetName}-local:
Patterns *
Create Near
Sync All
Expunge None
SyncState *
'';
};
}
7.30.12. MSMTP
This is the program I use to send email from emacs. It is really the same thing as above,
just set the options to the ones you want in your system default.nix.
{ lib, config, ... }:
{
programs.msmtp = {
enable = lib.mkDefault config.monorepo.profiles.email.enable;
extraConfig = ''
# Set default values for all following accounts.
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
tls_certcheck off
logfile ~/.msmtp.log
# Gmail
account ${config.monorepo.vars.userName}
host ${config.monorepo.profiles.email.smtpsServer}
port 587
from ${config.monorepo.profiles.email.email}
user ${config.monorepo.profiles.email.email}
passwordeval "cat ${config.sops.secrets.mail.path}"
# Set a default account
account default : ${config.monorepo.vars.userName}
'';
};
}
7.30.13. Mpd
This mpd configuration uses pipewire by default, and it should just work if you place music
in the ~/music directory and then run mpc add / afterwards.
{ lib, config, ... }:
{
services.mpd = {
enable = lib.mkDefault config.monorepo.profiles.music.enable;
dbFile = "/home/${config.monorepo.vars.userName}/.config/mpd/db";
dataDir = "/home/${config.monorepo.vars.userName}/.config/mpd/";
network.port = 6600;
musicDirectory = "/home/${config.monorepo.vars.userName}/music";
playlistDirectory = "/home/${config.monorepo.vars.userName}/.config/mpd/playlists";
network.listenAddress = "0.0.0.0";
extraConfig = ''
audio_output {
type "pipewire"
name "pipewire output"
}
audio_output {
type "httpd"
name "Ret2pop's Music Stream"
encoder "opus" # optional
port "8000"
bitrate "128000"
format "48000:16:1"
always_on "yes"
tags "yes"
}
audio_output {
type "shout"
name "My VPS Stream"
host "127.0.0.1"
port "8888" # This must match your SSH tunnel local port
mount "/stream" # The URL path (e.g. http://vps:8000/stream)
password "SuperSecretSourcePass"
bitrate "128"
format "44100:16:2"
protocol "icecast2" # Essential for modern Icecast
user "source" # Default icecast source user
description "My MPD Stream"
genre "Mixed"
}
'';
};
}
7.30.14. MPV
I have some emacs + yt-dlp integrations with mpv with my rss feed, and therefore we need it here:
{ lib, config, ... }:
{
programs.mpv = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
config = {
profile = "gpu-hq";
force-window = true;
ytdl-format = "bestvideo+bestaudio";
cache-default = 4000000;
};
};
}
7.30.15. GTK
{ lib, config, pkgs, ... }:
{
gtk = {
theme = {
name = "catppuccin-mocha-pink-standard";
package = pkgs.catppuccin-gtk.override {
variant = "mocha";
accents = [ "pink" ];
};
};
};
xdg.configFile = {
"gtk-4.0/assets".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/assets";
"gtk-4.0/gtk.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/gtk.css";
"gtk-4.0/gtk-dark.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/gtk-dark.css";
"gtk-3.0/gtk.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-3.0/gtk.css";
"gtk-3.0/gtk-dark.css".source = "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-3.0/gtk-dark.css";
"gtk-3.0/settings.ini".text = ''
[Settings]
gtk-theme-name=${config.gtk.theme.name}
gtk-application-prefer-dark-theme=1
'';
};
}
7.30.16. Waybar
This is the bar I use for my hyprland configuration. You will need to adjust the monitors field
in the default.nix for it to really appear.
{ lib, config, ... }:
{
programs.waybar = {
enable = lib.mkDefault config.monorepo.profiles.hyprland.enable;
style = ''
* {
border: none;
border-radius: 0px;
font-family: Iosevka Nerd Font, FontAwesome, Noto Sans CJK;
font-size: 14px;
font-style: normal;
min-height: 0;
}
window#waybar {
background: rgba(30, 30, 46, 0.5);
border-bottom: 1px solid #45475a;
color: #cdd6f4;
}
#workspaces {
background: #45475a;
margin: 5px 5px 5px 5px;
padding: 0px 5px 0px 5px;
border-radius: 16px;
border: solid 0px #f4d9e1;
font-weight: normal;
font-style: normal;
}
#workspaces button {
padding: 0px 5px;
border-radius: 16px;
color: #a6adc8;
}
#workspaces button.active {
color: #f4d9e1;
background-color: transparent;
border-radius: 16px;
}
#workspaces button:hover {
background-color: #cdd6f4;
color: black;
border-radius: 16px;
}
#custom-date, #clock, #battery, #pulseaudio, #network, #custom-randwall, #custom-launcher {
background: transparent;
padding: 5px 5px 5px 5px;
margin: 5px 5px 5px 5px;
border-radius: 8px;
border: solid 0px #f4d9e1;
}
#custom-date {
color: #D3869B;
}
#custom-power {
color: #24283b;
background-color: #db4b4b;
border-radius: 5px;
margin-right: 10px;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 0px;
padding: 5px 10px;
}
#tray {
background: #45475a;
margin: 5px 5px 5px 5px;
border-radius: 16px;
padding: 0px 5px;
/*border-right: solid 1px #282738;*/
}
#clock {
color: #cdd6f4;
background-color: #45475a;
border-radius: 0px 0px 0px 24px;
padding-left: 13px;
padding-right: 15px;
margin-right: 0px;
margin-left: 10px;
margin-top: 0px;
margin-bottom: 0px;
font-weight: bold;
/*border-left: solid 1px #282738;*/
}
#battery {
color: #89b4fa;
}
#battery.charging {
color: #a6e3a1;
}
#battery.warning:not(.charging) {
background-color: #f7768e;
color: #f38ba8;
border-radius: 5px 5px 5px 5px;
}
#backlight {
background-color: #24283b;
color: #db4b4b;
border-radius: 0px 0px 0px 0px;
margin: 5px;
margin-left: 0px;
margin-right: 0px;
padding: 0px 0px;
}
#network {
color: #f4d9e1;
border-radius: 8px;
margin-right: 5px;
}
#pulseaudio {
color: #f4d9e1;
border-radius: 8px;
margin-left: 0px;
}
#pulseaudio.muted {
background: transparent;
color: #928374;
border-radius: 8px;
margin-left: 0px;
}
#custom-randwall {
color: #f4d9e1;
border-radius: 8px;
margin-right: 0px;
}
#custom-launcher {
color: #e5809e;
background-color: #45475a;
border-radius: 0px 24px 0px 0px;
margin: 0px 0px 0px 0px;
padding: 0 20px 0 13px;
/*border-right: solid 1px #282738;*/
font-size: 20px;
}
#custom-launcher button:hover {
background-color: #FB4934;
color: transparent;
border-radius: 8px;
margin-right: -5px;
margin-left: 10px;
}
#custom-playerctl {
background: #45475a;
padding-left: 15px;
padding-right: 14px;
border-radius: 16px;
/*border-left: solid 1px #282738;*/
/*border-right: solid 1px #282738;*/
margin-top: 5px;
margin-bottom: 5px;
margin-left: 0px;
font-weight: normal;
font-style: normal;
font-size: 16px;
}
#custom-playerlabel {
background: transparent;
padding-left: 10px;
padding-right: 15px;
border-radius: 16px;
/*border-left: solid 1px #282738;*/
/*border-right: solid 1px #282738;*/
margin-top: 5px;
margin-bottom: 5px;
font-weight: normal;
font-style: normal;
}
#window {
background: #45475a;
padding-left: 15px;
padding-right: 15px;
border-radius: 16px;
/*border-left: solid 1px #282738;*/
/*border-right: solid 1px #282738;*/
margin-top: 5px;
margin-bottom: 5px;
font-weight: normal;
font-style: normal;
}
#custom-wf-recorder {
padding: 0 20px;
color: #e5809e;
background-color: #1E1E2E;
}
#cpu {
background-color: #45475a;
/*color: #FABD2D;*/
border-radius: 16px;
margin: 5px;
margin-left: 5px;
margin-right: 5px;
padding: 0px 10px 0px 10px;
font-weight: bold;
}
#memory {
background-color: #45475a;
/*color: #83A598;*/
border-radius: 16px;
margin: 5px;
margin-left: 5px;
margin-right: 5px;
padding: 0px 10px 0px 10px;
font-weight: bold;
}
#disk {
background-color: #45475a;
/*color: #8EC07C;*/
border-radius: 16px;
margin: 5px;
margin-left: 5px;
margin-right: 5px;
padding: 0px 10px 0px 10px;
font-weight: bold;
}
#custom-hyprpicker {
background-color: #45475a;
/*color: #8EC07C;*/
border-radius: 16px;
margin: 5px;
margin-left: 5px;
margin-right: 5px;
padding: 0px 11px 0px 9px;
font-weight: bold;
}
'';
settings = {
mainBar = {
layer = "top";
position = "top";
height = 50;
output = config.monorepo.vars.monitors;
modules-left = [ "hyprland/workspaces" ];
modules-center = [ "hyprland/window" ];
modules-right = [ "battery" "clock" ];
battery = {
format = "{icon} {capacity}%";
format-icons = ["" "" "" "" "" ];
};
clock = {
format = "⏰ {:%a %d, %b %H:%M}";
};
};
};
};
}
7.30.17. Wofi
This is a run launcher for wayland. I also use it for my powermenu.
{ lib, config, ... }:
{
programs.wofi = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
settings = {
location = "bottom-right";
allow_markup = true;
show = "drun";
width = 750;
height = 400;
always_parse_args = true;
show_all = false;
term = "kitty";
hide_scroll = true;
print_command = true;
insensitive = true;
prompt = "Run what, Commander?";
columns = 2;
};
style = ''
@define-color rosewater #f5e0dc;
@define-color rosewater-rgb rgb(245, 224, 220);
@define-color flamingo #f2cdcd;
@define-color flamingo-rgb rgb(242, 205, 205);
@define-color pink #f5c2e7;
@define-color pink-rgb rgb(245, 194, 231);
@define-color mauve #cba6f7;
@define-color mauve-rgb rgb(203, 166, 247);
@define-color red #f38ba8;
@define-color red-rgb rgb(243, 139, 168);
@define-color maroon #eba0ac;
@define-color maroon-rgb rgb(235, 160, 172);
@define-color peach #fab387;
@define-color peach-rgb rgb(250, 179, 135);
@define-color yellow #f9e2af;
@define-color yellow-rgb rgb(249, 226, 175);
@define-color green #a6e3a1;
@define-color green-rgb rgb(166, 227, 161);
@define-color teal #94e2d5;
@define-color teal-rgb rgb(148, 226, 213);
@define-color sky #89dceb;
@define-color sky-rgb rgb(137, 220, 235);
@define-color sapphire #74c7ec;
@define-color sapphire-rgb rgb(116, 199, 236);
@define-color blue #89b4fa;
@define-color blue-rgb rgb(137, 180, 250);
@define-color lavender #b4befe;
@define-color lavender-rgb rgb(180, 190, 254);
@define-color text #cdd6f4;
@define-color text-rgb rgb(205, 214, 244);
@define-color subtext1 #bac2de;
@define-color subtext1-rgb rgb(186, 194, 222);
@define-color subtext0 #a6adc8;
@define-color subtext0-rgb rgb(166, 173, 200);
@define-color overlay2 #9399b2;
@define-color overlay2-rgb rgb(147, 153, 178);
@define-color overlay1 #7f849c;
@define-color overlay1-rgb rgb(127, 132, 156);
@define-color overlay0 #6c7086;
@define-color overlay0-rgb rgb(108, 112, 134);
@define-color surface2 #585b70;
@define-color surface2-rgb rgb(88, 91, 112);
@define-color surface1 #45475a;
@define-color surface1-rgb rgb(69, 71, 90);
@define-color surface0 #313244;
@define-color surface0-rgb rgb(49, 50, 68);
@define-color base #1e1e2e;
@define-color base-rgb rgb(30, 30, 46);
@define-color mantle #181825;
@define-color mantle-rgb rgb(24, 24, 37);
@define-color crust #11111b;
@define-color crust-rgb rgb(17, 17, 27);
* {
font-family: 'Iosevka Nerd Font', monospace;
font-size: 14px;
}
/* Window */
window {
margin: 0px;
padding: 10px;
border: 0.16em solid @lavender;
border-radius: 0.1em;
background-color: @base;
animation: slideIn 0.5s ease-in-out both;
}
/* Slide In */
@keyframes slideIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* Inner Box */
#inner-box {
margin: 5px;
padding: 10px;
border: none;
background-color: @base;
animation: fadeIn 0.5s ease-in-out both;
}
/* Fade In */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* Outer Box */
#outer-box {
margin: 5px;
padding: 10px;
border: none;
background-color: @base;
}
/* Scroll */
#scroll {
margin: 0px;
padding: 10px;
border: none;
background-color: @base;
}
/* Input */
#input {
margin: 5px 20px;
padding: 10px;
border: none;
border-radius: 0.1em;
color: @text;
background-color: @base;
animation: fadeIn 0.5s ease-in-out both;
}
#input image {
border: none;
color: @red;
}
#input * {
outline: 4px solid @red!important;
}
/* Text */
#text {
margin: 5px;
border: none;
color: @text;
animation: fadeIn 0.5s ease-in-out both;
}
#entry {
background-color: @base;
}
#entry arrow {
border: none;
color: @lavender;
}
/* Selected Entry */
#entry:selected {
border: 0.11em solid @lavender;
}
#entry:selected #text {
color: @mauve;
}
#entry:drop(active) {
background-color: @lavender!important;
}
'';
};
}
7.30.18. yt-dlp
A classic program that allows you to download from youtube. Also has integrations with mpv.
{ lib, config, ... }:
{
programs.yt-dlp = {
enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
settings = {
embed-thumbnail = true;
embed-subs = true;
sub-langs = "all";
downloader = "aria2c";
downloader-args = "aria2c:'-c -x8 -s8 -k1M'";
};
};
}
7.30.19. Zsh
My zsh config has some useful aliases that one should read through. Otherwise it is pretty standard.
{ lib, config, pkgs, systemHostName, ... }:
{
programs.zsh = {
enable = true;
initContent = ''
umask 0022
export EXTRA_CCFLAGS="-I/usr/include"
source ${pkgs.zsh-vi-mode}/share/zsh-vi-mode/zsh-vi-mode.plugin.zsh
export QT_QPA_PLATFORM="wayland"
export OLLAMA_MODEL="qwen3:14b"
'';
localVariables = {
EDITOR = "emacsclient --create-frame --alternate-editor=vim";
INPUT_METHOD = "fcitx";
QT_IM_MODULE = "fcitx";
GTK_IM_MODULE = "fcitx";
XMODIFIERS = "@im=fcitx";
XIM_SERVERS = "fcitx";
WXSUPPRESS_SIZER_FLAGS_CHECK = "1";
};
shellAliases = {
clone-secrets = "git clone ssh://\"$1\"/home/preston/secrets \"$HOME/secrets\"";
get-channel-id = "yt-dlp --print \"%(channel_id)s\" --playlist-end 1 \"$1\"";
se = "sops edit";
f = "vim $(fzf)";
e = "cd $(find . -type d -print | fzf)";
c = "clear";
g = "git";
v = "vim";
py = "python3";
build-installer = "nix build $HOME/monorepo/nix#nixosConfigurations.installer.config.system.build.isoImage";
rb = "sudo nixos-rebuild switch --flake $HOME/monorepo/nix#${systemHostName}";
nfu = "cd ~/monorepo/nix && git add . && git commit -m \"new flake lock\" && nix flake update";
usync = "rsync -azvP --chmod=\"Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r\" ~/website_html/ root@${config.monorepo.vars.remoteHost}:/var/www/${config.monorepo.vars.internetName}-website/";
usite
= "cd ~/src/publish-org-roam-ui && bash local.sh && rm -rf ~/website_html/graph_view; cp -r ~/src/publish-org-roam-ui/out ~/website_html/graph_view && rsync -azvP --chmod=\"Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r\" ~/website_html/ root@${config.monorepo.vars.remoteHost}:/var/www/${config.monorepo.vars.internetName}-website/";
sai = "eval \"$(ssh-agent -s)\" && ssh-add ~/.ssh/id_ed25519 && ssh-add -l";
};
loginExtra = ''
if [[ "$(tty)" = "/dev/tty1" ]]; then
exec Hyprland
fi
'';
};
}
7.30.20. User
This configuration is the backbone configuration for the default user. It specifies some generally useful packages and something every home should have, as well as some dependencies for these configurations.
{ lib, config, pkgs, ... }:
{
home = {
activation.startup-files = lib.hm.dag.entryAfter [ "installPackages" ] ''
if [ ! -d "/home/${config.monorepo.vars.userName}/email/${config.monorepo.vars.internetName}/" ]; then
mkdir -p /home/${config.monorepo.vars.userName}/email/${config.monorepo.vars.internetName}/
fi
if [ ! -d "/home/${config.monorepo.vars.userName}/music" ]; then
mkdir -p /home/${config.monorepo.vars.userName}/music
fi
if [ ! -d /home/${config.monorepo.vars.userName}/org ]; then
mkdir -p /home/${config.monorepo.vars.userName}/org
fi
if [ ! -d /home/${config.monorepo.vars.userName}/src ]; then
mkdir -p /home/${config.monorepo.vars.userName}/src
fi
touch /home/${config.monorepo.vars.userName}/org/agenda.org
touch /home/${config.monorepo.vars.userName}/org/notes.org
'';
enableNixpkgsReleaseCheck = false;
username = config.monorepo.vars.userName;
homeDirectory = "/home/${config.monorepo.vars.userName}";
stateVersion = "24.11";
packages = with pkgs; (if config.monorepo.profiles.graphics.enable then [
# wikipedia
# kiwix kiwix-tools
gnupg
unzip
mupdf
zathura
fzf
# passwords
age sops
# formatting
ghostscript texliveFull pandoc
# Emacs Deps
graphviz jq
# Apps
# octaveFull
vesktop grim swww vim kotatogram-desktop tg qwen-code element-desktop thunderbird jami
# Sound/media
pavucontrol alsa-utils imagemagick ffmpeg helvum
# Net
curl rsync git iamb ungoogled-chromium
# Tor
torsocks tor-browser
# For transfering secrets onto new system
magic-wormhole stow
# fonts
nerd-fonts.iosevka noto-fonts noto-fonts-cjk-sans noto-fonts-color-emoji fira-code font-awesome_6 victor-mono
(aspellWithDicts
(dicts: with dicts; [ en en-computers en-science ]))
# Misc.
pinentry-gnome3
x11_ssh_askpass
xdg-utils
acpilight
pfetch
libnotify
htop
(pkgs.writeShellScriptBin "help"
''
#!/usr/bin/env sh
# Portable, colored, nicely aligned alias list
# Generate uncolored alias pairs
aliases=$(cat <<'EOF'
${let aliases = config.programs.zsh.shellAliases;
in lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
"${name} -> ${value}"
) aliases)}
EOF
)
# Align and color using awk
echo "$aliases" | awk '
BEGIN {
GREEN="\033[0;32m";
YELLOW="\033[0;33m";
RESET="\033[0m";
maxlen=0;
}
{
# Split line on " -> "
split($0, parts, / -> /);
name[NR]=parts[1];
cmd[NR]=parts[2];
if(length(parts[1])>maxlen) maxlen=length(parts[1]);
}
END {
for(i=1;i<=NR;i++) {
# printf with fixed width for alias name
printf "%s%-*s%s -> %s%s%s\n", GREEN, maxlen, name[i], RESET, YELLOW, cmd[i], RESET;
}
}'
'')
(writeShellScriptBin "remote-build"
''
#!/bin/bash
nixos-rebuild --sudo --ask-sudo-password --target-host "$1" switch --flake $HOME/monorepo/nix#spontaneity
''
)
(writeShellScriptBin "install-vps"
''
#!/bin/bash
nix run github:nix-community/nixos-anywhere -- --generate-hardware-config nixos-generate-config $HOME/monorepo/nix/systems/spontaneity/hardware-configuration.nix --flake $HOME/monorepo/nix#spontaneity --target-host "$1"
'')
(writeShellScriptBin "secrets"
''
#!/bin/bash
cd "$HOME/secrets"
git pull # repo is over LAN
stow */ # manage secrets with gnu stow
cd "$HOME"
'')
] else [
pfetch
# net
curl
torsocks
rsync
]);
};
services = {
gpg-agent = {
pinentry.package = pkgs.pinentry-emacs;
enable = true;
extraConfig = ''
allow-emacs-pinentry
allow-loopback-pinentry
'';
};
};
xdg.mimeApps = {
enable = true;
defaultApplications = {
"x-scheme-handler/mailto" = "emacsclient-mail.desktop";
};
};
programs.bash.enable = true;
fonts.fontconfig.enable = true;
}
8. Systems
8.1. Home
This module dynamically imports the correct corresponding home.nix at the path.
{ config, sops-nix, ... }:
{
home-manager = {
backupFileExtension = "backup";
sharedModules = [
sops-nix.homeManagerModules.sops
];
useGlobalPkgs = true;
useUserPackages = true;
users."${config.monorepo.vars.userName}" = (import (./. + "/${config.networking.hostName}/home.nix"));
};
}
8.2. Common
These are the common includes for each of my systems. This ensures that we don't have to duplicate includes every time we want to add a new system. Also more common configuration can go here.
{ config, lib, ... }:
{
imports = [
./home.nix
../modules/default.nix
];
# Put configuration (e.g. monorepo variable configuration) common to all configs here
}
8.2.1. Home Manager Common
Also I want to have the same kind of file for the home namespace.
{ lib, config, ... }:
{
imports = [
../modules/home/default.nix
];
# Put configuration (e.g. monorepo variable configuration) common to all configs here
}
8.3. Continuity
This is pretty understandable, if you understand all the above.
{ ... }:
{
imports = [
../../disko/btrfs-simple.nix
../common.nix
];
config = {
monorepo = {
profiles = {
impermanence.enable = true;
};
vars = {
device = "/dev/sda";
fileSystem = "btrfs";
};
};
};
}
8.3.1. Home
Each system has a corresponding home configuration in order to set monorepo home options.
{ lib, config, pkgs, ... }:
{
imports = [
../home-common.nix
];
config.monorepo.profiles.workstation.enable = false;
}
8.4. Affinity
This is my configuration for my workstation. It runs ollama, as well as several other useful services.
{ config, lib, home-manager, ... }:
{
imports = [
../common.nix
../../disko/drive-simple.nix
];
config = {
zramSwap = {
enable = true;
algorithm = "zstd";
memoryPercent = 50;
};
monorepo = {
vars.device = "/dev/nvme0n1";
profiles = {
server.enable = false;
cuda.enable = true;
workstation.enable = true;
};
};
};
}
8.4.1. Home
I want cuda in home manager too.
{ lib, config, pkgs, ... }:
{
imports = [
../home-common.nix
];
config.monorepo = {
profiles.cuda.enable = true;
};
}
8.5. Spontaneity
Spontaneity is my VPS instance. Note that much of this is not fully reproducible; you must change the IPs yourself and you must change some DNS records to match what you have on your system after deployment.
{ config, lib, ... }:
let
ipv4addr = "66.42.84.130";
ipv6addr = "2001:19f0:5401:10d0:5400:5ff:fe4a:7794";
in
{
imports = [
../common.nix
../../disko/drive-bios.nix
# nixos-anywhere generates this file
./hardware-configuration.nix
];
config = {
monorepo = {
vars.device = "/dev/vda";
profiles = {
server.enable = true;
ttyonly.enable = true;
grub.enable = true;
pipewire.enable = false;
tor.enable = false;
home.enable = false;
};
};
boot.loader.grub.device = "nodev";
boot.kernel.sysctl = {
"net.ipv6.conf.ens3.autoconf" = 0;
# Keep accept_ra = 1 so you still get the default gateway/route!
"net.ipv6.conf.ens3.accept_ra" = 1;
};
systemd.network.enable = true;
systemd.network.networks."40-ens3" = {
matchConfig.Name = "ens3";
networkConfig = {
# This is the magic combo for Vultr:
IPv6AcceptRA = true; # Accept routes (so we know where the internet is)
IPv6PrivacyExtensions = false; # No random privacy IPs
};
ipv6AcceptRAConfig = {
UseAutonomousPrefix = false; # Do NOT generate an IP address from the RA
};
};
networking = {
useDHCP = lib.mkForce false;
networkmanager.enable = lib.mkForce false;
tempAddresses = "disabled";
extraHosts = ''
127.0.0.1 livekit.${config.monorepo.vars.orgHost}
127.0.0.1 matrix.${config.monorepo.vars.orgHost}
'';
interfaces.ens3.ipv4.addresses = [
{
address = ipv4addr;
prefixLength = 24;
}
];
interfaces.ens3.useDHCP = lib.mkForce false;
interfaces.ens3.ipv6.addresses = [
{
address = ipv6addr;
prefixLength = 64;
}
];
defaultGateway = "66.42.84.1";
firewall = {
allowedTCPPorts = [
80
143
443
465
587
993
3478
5349
6697
6667
7881
8443
8448
9418
];
allowedUDPPorts = [
3478 5349 7882
];
allowedUDPPortRanges = [
{ from = 49152; to = 65535; }
];
};
domains = {
enable = true;
baseDomains = {
"${config.monorepo.vars.remoteHost}" = {
a.data = ipv4addr;
aaaa.data = ipv6addr;
};
"${config.monorepo.vars.orgHost}" = {
a.data = ipv4addr;
aaaa.data = ipv6addr;
mx.data = [
{
preference = 10;
exchange = "mail.${config.monorepo.vars.orgHost}";
}
];
txt = {
data = "v=spf1 ip4:${ipv4addr} ip6:${ipv6addr} -all";
};
};
};
subDomains = {
"${config.monorepo.vars.remoteHost}" = {};
"notes.${config.monorepo.vars.remoteHost}" = {
a.data = "45.76.87.125";
};
"_dmarc.${config.monorepo.vars.orgHost}" = {
txt = {
data = "v=DMARC1; p=none";
};
};
"default._domainkey.${config.monorepo.vars.orgHost}" = {
txt = {
data = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsC9GpfjvQlldPrHAC7Yt+ZF0aduUIVV4j2+KUkF0j6NsrpOgvU6COWKQSod/B/qyPBLWf+w5P5YiJ9XnOgw6Db/I9C67eusEHnV/cbvokXLQjSBvXee1OEdrT9i+6iUgDeGWP4CrD1DcwvXzAcCI9exy3yALHVlbkyYvi0KAYofs8dVQ3JCwSCMlol71lA6ULJ2zbCIWeSOv9/C6QZ5HOIeeoFLesX6O/YvF4FYxWbSHy244TXYuczQKuayjKgD6e8gIT5WJRQj8IAWOQ2podWw6hSuB3Ig+ekoOfnl5ivJGOMbAzFTj8FtbS4ncyidLU1kIOeuLfiILeDDLlIeYTwIDAQAB";
};
};
"ntfy.${config.monorepo.vars.remoteHost}" = {};
"matrix.${config.monorepo.vars.remoteHost}" = {};
"www.${config.monorepo.vars.remoteHost}" = {};
"music.${config.monorepo.vars.remoteHost}" = {};
"mail.${config.monorepo.vars.remoteHost}" = {
};
"livekit.${config.monorepo.vars.orgHost}" = {};
"${config.monorepo.vars.orgHost}" = {};
"git.${config.monorepo.vars.orgHost}" = {};
"matrix.${config.monorepo.vars.orgHost}" = {};
"social.${config.monorepo.vars.orgHost}" = {};
"list.${config.monorepo.vars.orgHost}" = {};
"talk.${config.monorepo.vars.orgHost}" = {};
"mail.${config.monorepo.vars.orgHost}" = {};
"${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" = {};
};
};
};
};
}
8.6. Home
{ lib, config, pkgs, ... }:
{
imports = [
../home-common.nix
];
config.monorepo.profiles.enable = false;
}
8.7. Installer
My installer installs my systems almost completely without interaction. You can also make them install the exact version of the system that you want it to by pinning the commits to make it always work in the exact same deterministic way.
8.7.1. Commit Hash Pinning
Modify this to pin the installer image hash to make the installer image always work deterministically.
{
diskoCommitHash = "c8a0e78d86b12ea67be6ed0f7cae7f9bfabae75a";
monorepoCommitHash = "7d8501ebc97fc238d73dee4638ab58eb32a11dc1";
monorepoUrl = "https://github.com/ret2pop/monorepo";
}
8.7.2. ISO Default Profile
This contains the installation script I use to install my systems.
{ pkgs, config, lib, modulesPath, ... }:
let
commits = import ./commits.nix;
in
{
imports = [
(modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix")
];
networking = {
networkmanager = {
enable = true;
};
firewall = {
allowedTCPPorts = [ 22 ];
allowedUDPPorts = [ ];
};
wireless.enable = false;
};
services.openssh = {
enable = true;
ports = [ 22 ];
settings = {
PasswordAuthentication = false;
AllowUsers = null;
UseDns = true;
PermitRootLogin = lib.mkForce "prohibit-password";
};
};
users.extraUsers.root.password = "nixos";
users.extraUsers.nixos.password = "nixos";
users.users = {
root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICts6+MQiMwpA+DfFQxjIN214Jn0pCw/2BDvOzPhR/H2 preston@continuity-dell"
];
nixos = {
packages = with pkgs; [
git
curl
gum
(writeShellScriptBin "nix_installer"
''
#!/usr/bin/env bash
set -euo pipefail
if [ "$(id -u)" -eq 0 ]; then
echo "ERROR! $(basename "$0") should be run as a regular user"
exit 1
fi
cd "$HOME"
ping -q -c1 google.com &>/dev/null && echo "online! Proceeding with the installation..." || nmtui
if [ ! -d "$HOME/monorepo/" ]; then
git clone ${commits.monorepoUrl}
cd "$HOME/monorepo"
git checkout "${commits.monorepoCommitHash}"
cd "$HOME"
fi
gum style --border normal --margin "1" --padding "1 2" "Enter a password for the encrypted disk. If you're not installing a profile with an encrypted disk, you can leave this blank."
echo "$(gum input --password)" > /tmp/secret.key
gum style --border normal --margin "1" --padding "1 2" "Choose a system to install or select \`new\` in order to create a new system."
SYSTEM="$(gum choose $(find "$HOME/monorepo/nix/systems" -mindepth 1 -maxdepth 1 -type d -printf "%f\n" | grep -v -E 'installer'; printf "New"))"
if [[ "$SYSTEM" == "New" ]]; then
gum style --border normal --margin "1" --padding "1 2" "Choose a system name"
SYSTEM="$(gum input --placeholder "system name")"
gum style --border normal --margin "1" --padding "1 2" "Select a drive file or create a new drive file."
DRIVE="$(gum choose $(find "$HOME/monorepo/nix/disko" -mindepth 1 -maxdepth 1 -type f -printf "%f\n"; printf "New"))"
if [[ "$DRIVE" == "New" ]]; then
gum style --border normal --margin "1" --padding "1 2" "Choose a name to call your drive file."
DRIVE="$(gum input --placeholder "drive file name (ex: partition_scheme.nix)")"
fi
fi
if [ ! -d "$HOME/monorepo/nix/systems/$SYSTEM" ]; then
mkdir -p "$HOME/monorepo/nix/systems/$SYSTEM"
cp "$HOME/monorepo/nix/systems/continuity/home.nix" "$HOME/monorepo/nix/systems/$SYSTEM/home.nix"
cat > "$HOME/monorepo/nix/systems/$SYSTEM/default.nix" <<EOF
{ ... }:
{
imports = [
../includes.nix
../../disko/$DRIVE
];
# CHANGEME
config.monorepo.vars.drive = "/dev/sda";
}
EOF
gum style --border normal --margin "1" --padding "1 2" "Edit the system default.nix with options."
gum input --placeholder "Press Enter to continue" >/dev/null
vim "$HOME/monorepo/nix/systems/$SYSTEM/default.nix"
gum style --border normal --margin "1" --padding "1 2" "Edit the home default.nix with options."
gum input --placeholder "Press Enter to continue" >/dev/null
vim "$HOME/monorepo/nix/systems/$SYSTEM/home.nix"
sed -i "/hostnames = \[/,/];/ s/];/ \"$1\"\n ];/" "$HOME/monorepo/nix/flake.nix"
if [ ! -f "$HOME/monorepo/nix/disko/$DRIVE" ]; then
cp "$HOME/monorepo/nix/disko/drive-simple.nix" "$HOME/monorepo/nix/disko/$DRIVE"
gum style --border normal --margin "1" --padding "1 2" "Edit the drive file with your preferred partitioning scheme."
gum input --placeholder "Press Enter to continue" >/dev/null
vim "$HOME/monorepo/nix/disko/$DRIVE"
fi
cd "$HOME/monorepo" && git add . && cd "$HOME"
fi
nix --extra-experimental-features 'nix-command flakes' eval "$HOME/monorepo/nix#evalDisko.$SYSTEM" > "$HOME/drive.nix"
gum style --border normal --margin "1" --padding "1 2" "Formatting the drive is destructive!"
if gum confirm "Are you sure you want to continue?"; then
echo "Proceeding..."
else
echo "Aborting."
exit 1
fi
sudo nix --experimental-features "nix-command flakes" run "github:nix-community/disko/${commits.diskoCommitHash}" -- --mode destroy,format,mount "$HOME/drive.nix"
cd /mnt
sudo nixos-install --flake "$HOME/monorepo/nix#$SYSTEM"
target_user="$(ls /mnt/home | head -n1)"
if [ -z "$target_user" ]; then
echo "No user directories found in /mnt/home"
exit 1
fi
sudo cp -r "$HOME/monorepo" "/mnt/home/$target_user/"
echo "rebooting..."; sleep 3; reboot
'')
];
};
};
systemd = {
services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ];
targets = {
sleep.enable = false;
suspend.enable = false;
hibernate.enable = false;
hybrid-sleep.enable = false;
};
};
}