Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Jay Looney
630f9b0074 even more backups of things 2025-10-28 16:11:45 -05:00
Jay Looney
b8d125d448 backing up the working dir 2025-10-28 16:10:19 -05:00
49 changed files with 1663 additions and 76 deletions

View file

@ -79,6 +79,9 @@ nix build .#nixosConfigurations.installIso.config.system.build.images.iso
sudo mount -o loop result/iso/nixos-*.iso mnt sudo mount -o loop result/iso/nixos-*.iso mnt
ls mnt ls mnt
umount mnt umount mnt
# Manipulate sops-nix secrets
nix-shell -p sops --run "sops secrets/example.yaml"
``` ```
## Design Goals ## Design Goals
@ -110,6 +113,10 @@ umount mnt
``` ```
## Configuring and Accessing Secrets
I need to rip out agecrypt and go pure SOPS.
## References ## References
- [@shazow](https://github.com/shazow/) and https://github.com/shazow/nixfiles/ - [@shazow](https://github.com/shazow/) and https://github.com/shazow/nixfiles/

70
flake.bak Normal file
View file

@ -0,0 +1,70 @@
{
description = "Configuration for NixOS";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixos-hardware.url = "github:nixos/nixos-hardware";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
# flake-parts.url = "github:hercules-ci/flake-parts";
};
# https://nix.dev/tutorials/nix-language.html#named-attribute-set-argument
outputs = inputs@{self, nixpkgs, nixos-hardware, home-manager, sops-nix, ...}:
let
lib = import ./lib { inherit (nixpkgs) lib; };
in
{
# NOTE: Run `nix flake show` to see what this flake has to offer.
# TODO: Enable automated formatting with numtide/treefmt-nix
# `nixos-rebuild switch --flake .#<hostname>`
nixosConfigurations = {
neon = nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
system = "x86_64-linux";
modules = [
./hosts/neon
];
};
#lithium = nixpkgs.lib.nixosSystem {
#specialArgs = { inherit inputs; };
#system = "x86_64-linux";
#modules = [
#./hosts/lithium
#];
#};
lithium = lib.mkSystem {
users = [
{ name = "jml"; }
];
};
};
homeConfigurations = {
"jml" = home-manager.lib.homeManagerConfiguration {
modules = [
./users/jml/home.nix
];
};
};
# TODO: Implement a dev shell for working on this repository.
# Personally I'm using hx, nil, and git-agecrypt
# The only thing totally necessary which isn't captured by the flake inputs
# is git-agecrypt so that should be included here or somehow in the flake.
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = with pkgs; [
nixpkgs-fmt
sops
age
git-agecrypt
];
shellHook = ''
echo "ready to rock"
'';
};
# `nix run .#name` and `nix build .#name`
# packages = {};
# `home-manager switch`
# homeConfigurations = {};
};
}

146
flake.lock generated
View file

@ -22,11 +22,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1757508292, "lastModified": 1758287904,
"narHash": "sha256-7lVWL5bC6xBIMWWDal41LlGAG+9u2zUorqo3QCUL4p4=", "narHash": "sha256-IGmaEf3Do8o5Cwp1kXBN1wQmZwQN3NLfq5t4nHtVtcU=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "146f45bee02b8bd88812cfce6ffc0f933788875a", "rev": "67ff9807dd148e704baadbd4fd783b54282ca627",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -72,6 +72,24 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": { "gitignore": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -101,11 +119,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1749499854, "lastModified": 1759337100,
"narHash": "sha256-V1BgwiX8NjbRreU6LC2EzmuqFSQAHhoSeNlYJyZ40NE=", "narHash": "sha256-CcT3QvZ74NGfM+lSOILcCEeU+SnqXRvl1XCRHenZ0Us=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "1df816c407d3a5090c8496c9b00170af7891f021", "rev": "004753ae6b04c4b18aa07192c1106800aaacf6c3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -140,13 +158,35 @@
"type": "github" "type": "github"
} }
}, },
"microvm": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"spectrum": "spectrum"
},
"locked": {
"lastModified": 1758113222,
"narHash": "sha256-Q5i/qaj6v6F4N1Q5gI/4aL0IEEUE/LjQuwcA8L5IOMc=",
"owner": "astro",
"repo": "microvm.nix",
"rev": "b9206e245c07c0782beff58e1e94bb48b2531d15",
"type": "github"
},
"original": {
"owner": "astro",
"repo": "microvm.nix",
"type": "github"
}
},
"nixos-hardware": { "nixos-hardware": {
"locked": { "locked": {
"lastModified": 1749195551, "lastModified": 1759261527,
"narHash": "sha256-W5GKQHgunda/OP9sbKENBZhMBDNu2QahoIPwnsF6CeM=", "narHash": "sha256-wPd5oGvBBpUEzMF0kWnXge0WITNsITx/aGI9qLHgJ4g=",
"owner": "nixos", "owner": "nixos",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "4602f7e1d3f197b3cb540d5accf5669121629628", "rev": "e087756cf4abbe1a34f3544c480fc1034d68742f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -155,18 +195,37 @@
"type": "github" "type": "github"
} }
}, },
"nixos-secrets": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1759474546,
"narHash": "sha256-ZOlrxrAb5x5yM13P16sZowXb8AbEH02cZxets6SmkkA=",
"ref": "refs/heads/main",
"rev": "b28dde5afb05bf107905eff5c38908463fde5c59",
"shallow": true,
"type": "git",
"url": "ssh://forgejo@git.garage.systems/jml/nixos-secrets.git"
},
"original": {
"shallow": true,
"type": "git",
"url": "ssh://forgejo@git.garage.systems/jml/nixos-secrets.git"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1749285348, "lastModified": 1757967192,
"narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", "narHash": "sha256-/aA9A/OBmnuOMgwfzdsXRusqzUpd8rQnQY8jtrHK+To=",
"owner": "nixos", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", "rev": "0d7c15863b251a7a50265e57c1dca1a7add2e291",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "NixOS",
"ref": "nixos-unstable", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -187,6 +246,22 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": {
"locked": {
"lastModified": 1759381078,
"narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks-nix": { "pre-commit-hooks-nix": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
@ -219,8 +294,10 @@
"disko": "disko", "disko": "disko",
"home-manager": "home-manager", "home-manager": "home-manager",
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"microvm": "microvm",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs", "nixos-secrets": "nixos-secrets",
"nixpkgs": "nixpkgs_2",
"sops-nix": "sops-nix" "sops-nix": "sops-nix"
} }
}, },
@ -252,11 +329,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1747603214, "lastModified": 1759188042,
"narHash": "sha256-lAblXm0VwifYCJ/ILPXJwlz0qNY07DDYdLD+9H+Wc8o=", "narHash": "sha256-f9QC2KKiNReZDG2yyKAtDZh0rSK2Xp1wkPzKbHeQVRU=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "8d215e1c981be3aa37e47aeabd4e61bb069548fd", "rev": "9fcfabe085281dd793589bdc770a2e577a3caa5d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -264,6 +341,37 @@
"repo": "sops-nix", "repo": "sops-nix",
"type": "github" "type": "github"
} }
},
"spectrum": {
"flake": false,
"locked": {
"lastModified": 1754675037,
"narHash": "sha256-afS08F7lfMUBR4qrBxinN1kuxu+DoHQ5TPNVp9VS/OA=",
"ref": "refs/heads/main",
"rev": "586577f3015397afacd83bc185454f4cc3c8028f",
"revCount": 955,
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
},
"original": {
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -11,9 +11,14 @@
sops-nix.inputs.nixpkgs.follows = "nixpkgs"; sops-nix.inputs.nixpkgs.follows = "nixpkgs";
disko.url = "github:nix-community/disko"; disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs"; disko.inputs.nixpkgs.follows = "nixpkgs";
microvm.url = "github:astro/microvm.nix";
microvm.inputs.nixpkgs.follows = "nixpkgs";
nixos-secrets.url = "git+ssh://forgejo@git.garage.systems/jml/nixos-secrets.git?shallow=1";
#nixos-secrets.flake = false; # TODO: Why does flake need to be false?
#nixos-secrets.inputs.nixpkgs.follows = "nixpkgs";
}; };
# https://nix.dev/tutorials/nix-language.html#named-attribute-set-argument # https://nix.dev/tutorials/nix-language.html#named-attribute-set-argument
outputs = inputs@{self, nixpkgs, nixos-hardware, home-manager, sops-nix, lanzaboote, disko, ...}: outputs = inputs@{self, nixpkgs, nixos-hardware, home-manager, sops-nix, lanzaboote, disko, microvm, nixos-secrets, ...}:
let let
mkSystem = (import ./lib { mkSystem = (import ./lib {
inherit nixpkgs home-manager inputs; inherit nixpkgs home-manager inputs;
@ -29,7 +34,12 @@
}; };
lithium = mkSystem { lithium = mkSystem {
hostname = "lithium"; hostname = "lithium";
# extraModules = [ inputs.sops-nix.nixosModules.sops ]; #specialArgs = {inherit inputs;};
extraModules = [
inputs.sops-nix.nixosModules.sops
inputs.nixos-secrets.nixosModules.private-config
];
#extraModules = [ microvm.nixosModules.host ];
users = [ users = [
"jml" "jml"
"breakglass" "breakglass"

View file

@ -1,8 +1,8 @@
{ config, pkgs, ... }: { config, pkgs, lib, ... }:
{ {
sops.defaultSopsFile = ./secrets/common.yaml; #sops.defaultSopsFile = ./secrets/common.yaml;
networking.hostName = "lithium"; networking.hostName = "lithium";
networking.domain = config.vars.domain; networking.domain = lib.mkForce config.vars.domain;
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
zfs zfs
]; ];

View file

@ -1,16 +1,47 @@
{ inputs, ... }: { inputs, ... }:
#let
#secretsPath = builtins.toString inputs.nixos-secrets;
#in
{ {
#sops = {
#defaultSopsFile = "${secretsPath}/${config.hostname}/secrets.yaml";
#defaultSopsFile = "${secretsPath}/global/secrets.yaml";
#};
imports = [ imports = [
../../modules/nixos/base.nix ../../modules/nixos/base.nix
inputs.sops-nix.nixosModules.sops #inputs.sops-nix.nixosModules.sops
#import ./sops.nix ( {inherit inputs;} )
./sops.nix
./hardware.nix ./hardware.nix
./configuration.nix ./configuration.nix
./semi-secret-vars.nix ./semi-secret-vars.nix
./services/caddy.nix ./services/caddy.nix
./services/tailscale.nix ./services/tailscale.nix
./services/kanidm.nix ./services/kanidm.nix
./services/jellyfin.nix ./services/jellyfin.nix
./services/uptime-kuma.nix ./services/uptime-kuma.nix
./services/file-shares.nix ./services/file-shares.nix
./services/miniflux
./services/forgejo.nix
./services/immich.nix
./services/calibre-web.nix
# Monitoring
./services/monitoring
./services/smartd.nix
# Game Servers
./services/palworld
# TODO: Add Karakeep with yt-dlp + https://news.ycombinator.com/item?id=45595084
# Services running in virtual machines
#./microvms
#./microvms/palworld
]; ];
} }

View file

@ -0,0 +1,18 @@
{ config, pkgs, lib, ... }:
{
microvm.autostart = [
"palworld-server"
];
microvm = {
interfaces = [
{ type = "user"; id = "main-net"; }
{ type = "macvtap"; id = "vm-palworld"; }
];
# Interface Name on the Host
# Ethernet Address of MicroVM's interface.
# Locally administered have one of 2/6/A/E in the second nibble.
#interfaces = [{type = "tap";id = "vm-palworld";mac = "02:00:00:00:00:01";}];
};
}

View file

@ -0,0 +1,107 @@
{ config, pkgs, lib, ... }:
{
# Host Firewall
networking.firewall.allowedUDPPorts = [ 8211 ];
#networking.nat = {
#enable = true;
#enableIPv6 = true;
#externalInterface = "eth0";
#internalInterfaces = [ "microvm" ];
#};
microvm.vms.palworld-server = {
# Basic Requirements
# https://docs.palworldgame.com/getting-started/requirements
#hypervisor = "qemu";
vcpu = 4;
memory = 16348;
# Networking
interfaces = [{ type = "user"; id = "main-net"; }];
# Interface Name on the Host
# Ethernet Address of MicroVM's interface.
# Locally administered have one of 2/6/A/E in the second nibble.
#interfaces = [{type = "tap";id = "vm-palworld";mac = "02:00:00:00:00:01";}];
#forwardPorts = [
#{ proto = "udp"; from = "host"; host.port = 8211; guest.port = 8211; }
# Optional: If you need RCON or other ports, add them here
# { proto = "tcp"; from = "host"; host.port = 25575; guest.port = 25575; }
#];
# Persistent Data
sharedDirectories = [
{
source = "/var/lib/palworld-data";
target = "/var/lib/palworld-server";
readonly = false;
}
];
# VM NixOS Configuration
config = {
imports = [ pkgs.nixosModules.notDetected ];
networking.hostName = "palworld-vm";
time.timeZone = "America/Chicago";
environment.systemPackages = with pkgs; [
steamcmd
#glibc
#gnumake
#cff
];
# Pre-VM-Start
binScripts.tap-up = lib.mkAfter ''
${lib.getExe' pkgs.iproute2 "ip"} link set dev 'vm-ixp-as11201p' master 'ixp-peering'
'';
# Service Definition
systemd.services.palworld-dedicated = {
description = "Palworld Dedicated Server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "simple";
User = "palworld";
Group = "palworld";
# Working Directory points to where steamcmd installs the server
WorkingDirectory = "/var/lib/palworld-server/Pal/Binaries/Win64";
ExecStart = ''
${pkgs.steam-run}/bin/steam-run ${pkgs.bash}/bin/bash -c '\
${pkgs.steamcmd}/bin/steamcmd \
+force_install_dur /var/lib/palworld-server \
+login anonymous \
+app_update 2394010 validate \
+quit \
&& \
./PalServer.sh -userperfthreads -NoAsyncLoadingThread -UseNvidiaServers -nosteamclient \
-Players=8 -Port=8211 -queryport=27015 -PublicPort=8211 -PublicIP=\"\" -RCONEnabled=False
'
'';
Restart = "on-failure";
RestartSec = "5s";
LimitNPROC = 10000;
LimitNOFILE = 100000;
};
};
# User and Group Configuration
users.users.palworld = {
isSystem = true;
group = "palworld";
createHome = false;
};
users.groups.palworld = {};
# Firewall Configuration
networking.firewall.allowedUDPPorts = [ 8211 ];
# Ensure correct permissions for shared directory
systemd.tmpfiles.rules = [
"d /var/lib/palworld-server 0755 palworld palworld -"
];
};
};
}

View file

@ -0,0 +1,50 @@
{ config, lib, pkgs, ... }:
{
microvm.vms.valheim = {
autoStart = true;
memorySize = 4096;
vcpu = 2;
forwardPorts = [
{ from = "host"; hostPort = 2456; guestPort = 2456; proto = "udp"; }
{ from = "host"; hostPort = 2457; guestPort = 2457; proto = "udp"; }
];
# NOTE: For games with large save files, choose a path in "/tank" for
# storage.
sharedDirectories = [
{
hostPath = "/srv/game-data-valheim";
guestPath = "/data";
tag = "valheim-data";
readOnly = false;
}
];
packages = [ pkgs.steamcmd pkgs.steam-run ];
users.users.valheim = {
isNormalUser = true;
home = "/home/valheim";
extraGroups = [ "wheel" ];
};
systemd.services.valheim = {
description = "Valheim Dedicated Server";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
workingDirectory = "/data";
ExecStart = ''
${pkgs.steam-run}/bin/steam-run ./valheim_server.x86_64 \
-name "Valheim NixOS" \
-port 2456 \
-world "FlatEarth" \
-password "secret" \
-public 1
'';
Restart = "always";
User = "valheim";
};
};
};
}

View file

@ -0,0 +1,8 @@
{ inputs, ... }:
# let secretsPath = builtins.toString inputs.nixos-secrets; in
{
#imports = [ inputs.nixos-secrets.nixosModules.private-config ];
# Enables a whole littany of private settings.
private-config.enable = true;
}

View file

@ -1,27 +1,30 @@
kanidm: kanidm:
admin-password: ENC[AES256_GCM,data:wNE9qWAjfp8tf29sn1Q6GYrbw8g=,iv:uzg971jGIVyEkEbcOm2W8dy4wVgWiL+4Ph/f/bnieI0=,tag:/yY1okvnJLYGw2OLBd2Zdg==,type:str] admin-password: ENC[AES256_GCM,data:Hvmo6YG2ZCoYdQOBOyPiS2XAm6I=,iv:qKu5vlT0HEqK3Mx3zgAA0OUA+B63rEXnq/P059mrweI=,tag:K+iTc8R30ClP8egzIEtXKA==,type:str]
idm-admin-password: ENC[AES256_GCM,data:jIWaXUgHjhp0bP/DrF1m+plzcvE=,iv:nNpIkg9FTbCncih1/pAk4o7teuk7Gf/nPXyrnpFx4no=,tag:WhhsjtEdyS3Zw4F7uF9APg==,type:str] idm-admin-password: ENC[AES256_GCM,data:Dvz2o6gY/G3igJFhaIQ4gj/OG/8=,iv:hDE+y8SKqRU8tNxnd7q4CE3GfOHOkMcODpqc43KiPfc=,tag:fU8AyFokVUhERUvi6W6bYw==,type:str]
forgejo-admin-password: ENC[AES256_GCM,data:d7a2pzSpaeZ0CQ==,iv:XB6Y41egclWzmyZe3g2Z9U1NcCilw3VTZNlym94h3IU=,tag:vR6jrXiBciPXzUmvxHzQNw==,type:str]
miniflux:
oauth2_client_secret: ENC[AES256_GCM,data:tZk6Ru5MQk+VJ/ulZNtKirL2lfLmOeVKsDbJfLly1SBwXC6HdjBx+yAFCc7YVX+2,iv:GIIMhV/sIALjoUZsMMXAJl968owAlULJ1JLpShqa3RM=,tag:c2i+LvGJKQm82fsUEHF/BA==,type:str]
sops: sops:
age: age:
- recipient: age1mv8xtvkuuw3hphq5ytaekz7p8a4kht79uajyhy534uy9e5472fhqj5zpxu - recipient: age1mv8xtvkuuw3hphq5ytaekz7p8a4kht79uajyhy534uy9e5472fhqj5zpxu
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5cFlReGMxV1R3QW1Vd1RU YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4WUlyeStvZ28xVmV4VlNK
WTgzNm5tbGhld3RMTGpMU1M2eVdoU0hmZGtNCmZLSTNOMk9IMDh6K2svRmRveGw3 d1J1Z0twaTFzSEFENit0S210eTJFYTR1c1FRCnQvcFRCMUtFMkJxUDlRUTNxcTlK
dlFUZ2lzTDBJWnBSTEhVTmVDOHVTdW8KLS0tIDhLWVZWSVJYM2x4YTlZWWZZZVNh dE5aM3U4Y0xvRE5jZlhrbjVtVk5rOHMKLS0tIHZISVlVcW9yT3hWcUtsVmN5TmRv
T2drb0p6TjZrZldpU0VUd0xmcVJUSk0KMjX3vr/74/HU7fmulefUHiNzwX8LcAes bXk5aSs4Wk9nM1p5d3FVSFBFKzVYZVEKYP2KQZIYm+zuI6OTfy85cEhj3gJWoKNu
ob3fabhMk9lmbuQk21rpoWbz3PNTfCQH63q+h7gLJTCCW2ISTvh/KQ== jxd8vxwSbDmsXQK+mT8MsA9s+A9AhzGcZQ0rIQM/yKWFKSXt4kJ9rg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age148yre4vaxp6lm59rft24te46szawqyguf8znkrtpq7ud8tpteauqxkwyjl - recipient: age148yre4vaxp6lm59rft24te46szawqyguf8znkrtpq7ud8tpteauqxkwyjl
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpK3VvNUhHNmJUQVZlREJl YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHQk1kYjU1bXZ0aUc3cGVQ
c1oyZWx4ekNEM0VqL3NKanNmSmZtQXNpcWdVCmpWcklLVnhWUisvcGZzV1NHN3p4 N1hwclNpRkJWQlVhaU9EOEFNRkRQMmhLakNjCjAwbE1UYkxxbzNxQjV5Y1I0TU0r
bW4wL09wL01XaHpveGdmbU4rbEp5NmsKLS0tIElhQVRmS05xUmJIZlI0S1dyWGhV cmZBUTZsTzMxZmFZZUxnRmw3T205aDAKLS0tIGdJTHBHVmk4TmlBQ1RHYkJ3aWw3
OGtKdHVwbWY2akJTQkF4YzlnNWQzNU0K81PyJ1tOvwOohNu9iUkS8vE7UXFRnJab bktSejFIdS8wS2R4RHRFMEwwb2pBN1kK8EoLo1E/DiFmpCf/v0kGLPcqIB1qZDd1
8OLHtzX7FrkIH8rO2D5vEL9gPmxUtNKc9Ad3sndQls/yfg4wJAYedA== Uf7ccMFqvQH8wGVuyqqwiZ2SconvK7hHC5U9qgi6bZa/t4aW8eeU/g==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-07T02:02:46Z" lastmodified: "2025-10-03T06:48:03Z"
mac: ENC[AES256_GCM,data:7mWOon8Hs2oU40l1dx4tVE8yXgcKoxfUAzY8zbtTzXqCOhzTzhY4OZAZiu4RiUSIOm7dMdQbH9zSx0j+5e2k9QflfJDDM3rWfunTa7L8Bm8k9b/WjS0Fnb7OV0InO6tLxQwkTamMcc7ORrKxwHB5PwuXD+efeWNXveHo5GYgF+M=,iv:seh2Pzt+AmmxyD5hwh3VkLQTDMq0Gh9mV6J3QrtxcmM=,tag:jpXn2fht8wArB2KD/ZmbyA==,type:str] mac: ENC[AES256_GCM,data:tglsrKi8Ydifc08LLA/KHzqWI3u9+Tn9kkERI3XKp/vSy4a90T2fU9f/lcyYbOXXuOOCL364wYq7ETmsTzOmb5Eo2Wtcu9Hsn820rLXuxNu7kyz9os/0eL/IThrPQtKov4/IdzoGQCqZvd9kcxTN1UPlRnd9aiHwsKmoFFFNZlE=,iv:y9+JQRXZf/6GabaQ1nQk3dL7qKMQxyzvIsXU2yVmlZo=,tag:klmWeIP068BpsSi8u/tE4A==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.10.2 version: 3.10.2

View file

@ -0,0 +1,10 @@
ADMIN_USERNAME=ENC[AES256_GCM,data:1b9RG0hCr3mV+Q==,iv:+P9LxMk7gLvCvVgrzRgHhk7s3kMnMNkWyT8cyBe0msI=,tag:2TVfhdVToEsz4mF/UrrP9w==,type:str]
ADMIN_PASSWORD=ENC[AES256_GCM,data:3HUEKnmGJ9vRSA==,iv:UpY8iFBIT01QUFINo3Cn7dT6q0uDbbDNuU2K7hjfaRY=,tag:LizvS3rkk7PHQVTLRtmbqw==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0TWxwa21pNmxJczd1Y3l2\nZjIzYWJ2TEk0VlZGTXN2a3lvaGR3Q2cwNXlBClN5dXJyMmxFWnA5UytVUjErZ1Ju\nZHY5emdBd2plM1dvRWNwaE5ydW9IbGcKLS0tIEFtUFZnUkllajhDVTU4ejlTN09n\nN0VTWi9lb1UxQm1OTGwzU2dkc25XR1EKlsqqZCJ6YTseRoxaoQRcnnqg0Q6zDezP\ni8lVt4WCK/mIXiqASNf0EaunujxftSg4g5s1PoUzdh2RWRXjjUk6Rw==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1mv8xtvkuuw3hphq5ytaekz7p8a4kht79uajyhy534uy9e5472fhqj5zpxu
sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCY1pnNWdpUGNmTXQ5VHY4\nazJaSUVnMEd4cTVIa3p1NWJBWHAyMEhuMXprCjk3MENuUDJ3OE13bUhJKzNxV1Yr\nZmMwMUtyZldodDRBRkRIUnN5cCswVHcKLS0tIE43RmFHQU0zMitrcmtKaGRMTXZZ\nZWdCOHNVaXp2WjNWUUtTNmVjQTdQMkUK7dbXoDOAxppQi3nmp0S7q130BBvxtdo/\nuaC5FuUXijp0U+Xrdkq3Qukv/u4b7E17mw3zMgHIaxf8w0W/XmTcXQ==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_1__map_recipient=age148yre4vaxp6lm59rft24te46szawqyguf8znkrtpq7ud8tpteauqxkwyjl
sops_lastmodified=2025-07-22T01:07:11Z
sops_mac=ENC[AES256_GCM,data:I17CIK+OqttRpVc9U3Xdefnb95PFcFpCykLSxoVvO74NCvYcHYaB09Wnbt5X46u3TtX1ZmDr85WNzWocu+rM1kdJFXnGI/N9UiCgnam66QCwIlIckCldDGsnPbHpkeB7SFgCJ+1GOz19poPu2RJ41b28NRxNdV4+9HB15Xz6OcM=,iv:7u28+i8HA6wv9SuTSjCGr5o0Wak6zmUvSj3z3CzxT6U=,tag:GXEbSyBGkPSveD0zREBEyQ==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.10.2

View file

@ -0,0 +1,11 @@
# hosts/lithium/services
The idea is that each `*.nix` or each `./*/default.nix` file would contain all
necessary details for a service to bring itself up and be running.
One thing I have overlooked thus far is nothing tests for the existence of a
reverse proxy and bails out if one isn't available. Practically if caddy isn't
running, most of these services should also not run, or at the very least, the
blocks pertaining to setting up reverse proxy details don't need to run.
There's a way of doing that with things like lib.mkDefault and so forth.

View file

@ -0,0 +1,65 @@
{ config, ... }:
let
homelabDomain = inputs.nixos-secrets.homelabDomain;
certDir = config.security.acme.certs."${homelabDomain}".directory;
in
{
sops.secrets."cloudflare/dns_api_token" = {
mode = "0440";
group = config.services.caddy.group;
restartUnits = [ "caddy.service" "ddclient.service" ];
};
# TODO: Consider defining reverse proxy all in one location.
# All the ports and domains would be visible in one place.
security.acme = {
acceptTerms = true;
defaults = {
# NOTE: Uncomment the following line for testing, comment for production.
server = "https://acme-staging-v02.api.letsencrypt.org/directory";
dnsProvider = "cloudflare";
dnsResolver = "1.1.1.1:53";
dnsPropagationCheck = true;
credentialFiles = {
CLOUDFLARE_DNS_API_TOKEN_FILE = config.sops.secrets."cloudflare/dns_api_token".path;
};
group = config.services.caddy.group;
#reloadServices = [ "caddy" ];
email = "admin+acme@${homelabDomain}"; # NOTE: This email is /dev/null;
#keyType = "ec384";
};
};
services.ddclient = {
enable = true;
protocol = "cloudflare";
usev4 = "webv4, webv4=https://cloudflare.com/cdn-cgi/trace, web-skip='ip='";
username = "token";
#secretsFile = config.sops.secrets."cloudflare/dns_api_token".path;
passwordFile = config.sops.secrets."cloudflare/dns_api_token".path;
zone = homelabDomain;
domains = [
homelabDomain
"*.${homelabDomain}"
"id.${homelabDomain}"
"status.${homelabDomain}"
"grafana.${homelabDomain}"
"feeds.${homelabDomain}"
"git.${homelabDomain}"
"tv.${homelabDomain}"
"demo.${homelabDomain}" # Testing to see if the DNS record is set.
];
};
# NOTE: Issue a single cert /w subdomain wildcard
# At the expense of individual service security, some public details about
# attack surface remain slightly more private in https://crt.sh/
security.acme.certs."${homelabDomain}" = {
#group = config.services.caddy.group;
domain = "${homelabDomain}";
extraDomainNames = [ "*.${homelabDomain}" ];
};
# Nginx useACMEHost provides the DNS-01 challenge.
# security.acme.certs."${homelabDomain}".directory
}

View file

@ -0,0 +1,20 @@
{ inputs, config, pkgs, lib, ... }:
let
homelabDomain = inputs.nixos-secrets.homelabDomain;
svcDomain = "audiobooks.${homelabDomain}";
svcPort = config.services.audiobookshelf.port; # Prevent a Conflict
in
{
services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${svcPort}
'';
services.audiobookshelf = {
enable = true;
openFirewall = true;
port = 8000;
# NOTE: Path to AudioBookShelf config & metadata inside of `/var/lib`
dataDir = "audiobookshelf";
};
}

View file

@ -1,13 +1,22 @@
{ config, pkgs, ... }: { inputs, config, pkgs, lib, ... }:
let
homelabDomain = inputs.nixos-secrets.homelabDomain;
certDir = config.security.acme.certs."${homelabDomain}".directory;
in
{ {
sops.secrets.caddy_env = { services.nginx.enable = lib.mkForce false;
sopsFile = ../secrets/caddy.env;
format = "dotenv"; sops.secrets.cloudflare_env = {
mode = "0440"; mode = "0440";
owner = config.services.caddy.user; sopsFile = "${inputs.nixos-secrets}/lithium/cloudflare.env";
format = "dotenv";
group = config.services.caddy.group; group = config.services.caddy.group;
restartUnits = [ "caddy.service" ]; restartUnits = [ "caddy.service" ];
}; };
# TODO: Revert to using Caddy DNS for the whole thing.
# TODO: Add another cloudflare DDNS provider.
# TODO: Add Metrics with Prometheus & Grafana
services.caddy = { services.caddy = {
enable = true; enable = true;
package = pkgs.caddy.withPlugins { package = pkgs.caddy.withPlugins {
@ -16,26 +25,33 @@
"github.com/mholt/caddy-dynamicdns@v0.0.0-20250430031602-b846b9e8fb83" "github.com/mholt/caddy-dynamicdns@v0.0.0-20250430031602-b846b9e8fb83"
"github.com/caddy-dns/cloudflare@v0.2.1" "github.com/caddy-dns/cloudflare@v0.2.1"
]; ];
# NOTE: Built on 9/30/2025
# NOTE: Built on 6/4/2025 hash = "sha256-xuwNkxZop+RnzFtM9DEwah95nPSyx8KgM+Eu4EJ9kqI=";
hash = "sha256-swskhAr7yFJX+qy0FR54nqJarTOojwhV2Mbk7+fyS0I=";
}; };
# NOTE: Use Staging CA while testing, check `systemctl status caddy` # NOTE: Use Staging CA while testing, check `systemctl status caddy`
# to see if everything is working. # to see if everything is working.
# acmeCA = "https://acme-staging-v02.api.letsencrypt.org/directory"; # acmeCA = "https://acme-staging-v02.api.letsencrypt.org/directory";
# TODO: Add Metrics with Prometheus & Grafana environmentFile = config.sops.secrets.cloudflare_env.path;
environmentFile = config.sops.secrets.caddy_env.path; # NOTE: DNS provider settings
# https://caddy.community/t/how-to-use-dns-provider-modules-in-caddy-2/8148
globalConfig = '' globalConfig = ''
# acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} #acme_dns cloudflare {$CLOUDFLARE_DNS_API_TOKEN}
dynamic_dns { dynamic_dns {
provider cloudflare {env.CLOUDFLARE_API_TOKEN} provider cloudflare {$CLOUDFLARE_DNS_API_TOKEN}
check_interval 30m
ttl 5m
domains { domains {
${config.networking.domain} @ ${homelabDomain} @
} }
dynamic_domains dynamic_domains
} }
''; '';
}; };
networking.firewall.allowedTCPPorts = [ 80 443 ]; networking.firewall = {
allowedTCPPorts = [ 80 443 ];
allowedUDPPorts = [ 443 ];
};
} }

View file

@ -0,0 +1,53 @@
{ inputs, config, pkgs, lib, ... }:
let
homelabDomain = inputs.nixos-secrets.homelabDomain;
#certDir = config.security.acme.certs."${homelabDomain}".directory;
svcDomain = "books.${homelabDomain}";
svcHttpPort = config.services.calibre-web.listen.port;
web_data_dir = "calibre-web";
# TODO: I want the actual media stored in the tank.
library_path = "/tank/media/library/books";
#library_path = "/var/lib/calibre-library";
in
{
# TODO: This isn't the right place for this, but we need to guarantee that a
# media group exists.
users.users.calibre-web.extraGroups = [ "media" ];
users.groups.media = {};
services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy localhost:8883
'';
# reverse_proxy :${toString svcHttpPort}
# encode {
# zstd
# gzip
# minimum_length 1024
# }
# '';
# NOTE: Needs some manual setup in Web-UI and I ecountered issues connecting even with firewall enabled.
# The following command is what I used to forward the port:
# ssh -f -N -L localhost:8883:localhost:8883 jml@lithium
services.calibre-web = {
enable = true;
listen.port = 8883;
# NOTE: Don't need to open calibre-web port, it's served by reverse_proxy
openFirewall = true; # TODO: Temporarily opened to allow configuration from inside my network.
user = "calibre-web";
group = "calibre-web";
# Either absolute path or directory name under "/var/lib"
# /tank/media/library/books
dataDir = web_data_dir;
options = {
enableBookUploading = true;
enableBookConversion = true;
# NOTE: If I don't already have an extant calibreLibrary, I need to leave this null or the app won't launch.
calibreLibrary = library_path;
};
};
}

View file

@ -0,0 +1,145 @@
{ config, pkgs, lib, ... }:
let
homelabDomain = config.networking.domain;
svcDomain = "git.${homelabDomain}";
theme = pkgs.fetchzip {
url = "https://github.com/catppuccin/gitea/releases/download/v1.0.2/catppuccin-gitea.tar.gz";
hash = "sha256-rZHLORwLUfIFcB6K9yhrzr+UwdPNQVSadsw6rg8Q7gs=";
stripRoot = false;
};
svcHttpPort = config.services.forgejo.settings.server.HTTP_PORT;
assetsDir = "${config.services.forgejo.stateDir}/custom/public/assets";
in
{
# NOTE: Periodically come update the catpuccin theme.
# `-auto` will automatically switch between latte and mocha modes.
services.forgejo.settings.ui = {
DEFAULT_THEME = "catpuccin-teal-auto";
THEMES = builtins.concatStringsSep "," (
[ "auto" ]
++ (map (name: lib.removePrefix "theme-" (lib.removeSuffix ".css" name)) (
builtins.attrNames (builtins.readDir theme)
))
);
};
# TODO: Setup a PostgreSQL Server.
# Inspiration here: https://github.com/nyawox/arcanum/blob/4629dfba1bc6d4dd2f4cf45724df81289230b61a/nixos/servers/forgejo.nix#L64
#sops-secrets.postgres-forgejo = {
#sopsFile = ../secrets/forgejo.yaml;
#};
services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${toString svcHttpPort}
'';
services.forgejo = {
enable = true;
# database.type = "postgres";
settings = {
default.APP_NAME = "GitGarage";
server = {
DOMAIN = svcDomain;
ROOT_URL = "https://${svcDomain}";
HTTP_PORT = 3000;
};
# NOTE: Actions support is based on: https://github.com/nektos/act
#actions = {
#ENABLED = true;
#DEFAULT_ACTIONS_URL = "github";
#};
actions.ENABLED = false;
# NOTE: Registration is handled with kanidm.
# Registration button link is at /user/sign_up
service = {
REGISTER_EMAIL_CONFIRM = false;
DISABLE_REGISTRATION = false;
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
SHOW_REGISTRATION_BUTTON = false;
REQUIRE_SIGNIN_VIEW = false;
# TODO: Consider setting up emails.
ENABLE_NOTIFY_MAIL = false;
};
openid = {
ENABLE_OPENID_SIGNIN = true;
ENABLE_OPENID_SIGNUP = true;
WHITELISTED_URIS = "id.${homelabDomain}";
};
# TODO: Literally review all server settings, and link the forgejo documentation.
# Also perhaps include every setting here explicitly.
oauth2_client = {
REGISTER_EMAIL_CONFIRM = false;
ENABLE_AUTO_REGISTRATION = true;
ACCOUNT_LINKING = "login";
USERNAME = "nickname";
UPDATE_AVATAR = true;
OPENID_CONNECT_SCOPES = "openid email profile";
};
repository = {
DEFAULT_PRIVATE = "private";
DEFAULT_BRANCH = "main";
ENABLE_PUSH_CREATE_USER = true;
ENABLE_PUSH_CREATE_ORG = true;
};
mailer.ENABLED = false;
};
};
# TODO: Finish Configuring the kandim oauth for forgejo....
services.kanidm.provision.systems.oauth2.forgejo = {
displayName = "forgejo";
# TODO: Get this from Forgejo
# originUrl = "https://git.${homelabDomain}/user/oauth2/${homelabDomain}/callback";
originUrl = "${config.services.forgejo.settings.server.ROOT_URL}/user/oauth2/kanidm/callback";
originLanding = "https://git.${homelabDomain}/";
#basicSecretFile = "TODO!SETME";
scopeMaps."git.users" = [
"openid"
"email"
"profile"
"groups"
];
# WARNING: PKCE is currently not supported by gitea/forgejo,
# see https://github.com/go-gitea/gitea/issues/21376
allowInsecureClientDisablePkce = true;
preferShortUsername = true;
claimMaps.groups = {
joinType = "array";
valuesByGroup."git.admins" = [ "admin" ];
};
};
systemd.services.forgejo = {
preStart =
lib.mkAfter # bash
''
echo "Installing Catppuccin Assets"
rm -rf ${assetsDir}
mkdir -p ${assetsDir}
ln -sf ${theme} ${assetsDir}/css
'';
};
#sops.secrets.forgejo-runner-token = {};
#services.gitea-actions-runner = {
#package = pkgs.forgejo-runner;
#instances.default = {
#enable = true;
#name = "monolith";
#url = "https://${serviceDomain}";
#tokenFile = config.sops.secrets.forgejo-runner-token.path;
# NOTE: I don't want huge images if it can be avoided.
# https://nektosact.com/usage/runners.html
#labels = [
#"ubuntu-latest:docker://node:16-bullseye-slim"
#"ubuntu-22.04:docker://node:16-bullseye-slim"
#];
#};
#};
# TODO: Consider automatically creating admin account and password...
# https://wiki.nixos.org/wiki/Forgejo#Ensure_users
# Might be necessary to generate a token for kanidm
sops.secrets."forgejo/admin-password".owner = "forgejo";
}

View file

@ -0,0 +1,42 @@
{ pkgs, ... }:
{
# TODO
# systemd.services.<name>.serviceConfig.{MemoryMax,CPUQuota}
systemd.services.valheim-server = {
description = "Valheim dedicated server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
};
serviceConfig = {
Type = "simple";
User = "valheim";
Group = "valheim";
ExecStart = "${pkgs.steamcmd}/bin/steamcmd +login anonymous +force_install_dir /home/valheim/server +app_update 896660 validate +exit && /home/valheim/server/valheim_server.x86_64";
};
users.users.valheim = {
isSystemUser = true;
group = "valheim";
home = "/home/valheim";
};
users.groups.valheim = {};
networking.firewall = {
allowedTCPPorts = [ 7777 2456 ];
allowedUDPPorts = [ 7777 2457 ];
};
services.restic.backups.gameservers = {
user = "root";
# TODO: Pick a real backup directory.
repository = "/backup/gameservers";
paths = [
"/var/lib/terraria"
"/home/valheim/server"
];
timeConfig = {
OnCalendar = "daily";
};
};
}

View file

@ -0,0 +1,7 @@
{ config, pkgs, ... }:
{
services.home-assistant = {
enable = true;
openFirewall = true;
};
}

View file

@ -0,0 +1,87 @@
{ inputs, config, pkgs, lib, ... }:
let
homelabDomain = inputs.nixos-secrets.homelabDomain;
svcDomain = "photos.${homelabDomain}";
photoStorageDir = "/tank/shares/photos";
svcPort = config.services.immich.port;
# https://docs.immich.app/install/config-file/
jsonSettings = {
server.externalDomain = "https://${svcDomain}";
oauth = {
enabled = true;
issuerUrl = "https://"; # TODO: the kanidm url?
clientId = "immich";
clientSecret = config.sops.placeholder."immich/oauth2_client_secret";
scope = "openid email profile";
signingAlgorithm = "ES256";
storageLabelClaim = "email";
buttonText = "Login with Kanidm";
autoLaunch = true;
mobileOverrideEnabled = true;
mobileRedirectUri = "https://${svcDomain}/api/oauth/mobile-redirect/";
};
};
in
{
# NOTE: The following repo contains a highly mature immich setup on nixos.
# https://github.com/xinyangli/nixos-config/blob/a8b5bea68caea573801ccfdb8ceacb7a8f2b0190/machines/agate/services/immich.nix
services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${toString svcPort}
'';
# NOTE: Primarily to contain DB_PASSWORD to make it possible to backup and restore the DB.
# sops.secrets.immich_env = {
# sopsFile = ../../secrets/immich.env;
# format = "dotenv";
# mode = "0440";
# owner = "immich";
# group = "immich";
# restartUnits = [ "immich.service" ];
# };
sops.secrets."immich/oauth2_client_secret" = { };
sops.templates."immich.json" = {
mode = "0440";
owner = config.services.immich.user;
group = config.services.immich.group;
content = builtins.toJSON jsonSettings;
};
users.users.immich = {
isSystemUser = true;
};
users.groups.immich = {};
systemd.tmpfiles.rules = [
"d ${photoStorageDir} 0770 immich immich -"
];
# TODO: Setup mTLS for external / non-tailscale VPN immich access.
# https://github.com/alangrainger/immich-public-proxy/blob/main/docs/securing-immich-with-mtls.md
# TODO: Consider immich-public-proxy for generating "share" links
# https://github.com/alangrainger/immich-public-proxy
services.immich = {
enable = true;
openFirewall = true;
port = 2283; # default
#secretsFile = config.sops.secrets.immich_env.path;
# TODO: Build this directory with permissions for the immich user.
mediaLocation = "/tank/shares/photos";
environment = {
IMMICH_CONFIG_FILE = config.sops.templates."immich.json".path;
};
};
services.kanidm.provision.systems.oauth2.immich = {
displayName = "immich";
originUrl = "https://${svcDomain}/oauth2/oidc/callback";
originLanding = "https://${svcDomain}/";
basicSecretFile = config.sops.secrets."immich/oauth2_client_secret".path;
scopeMaps."immich.users" = [
"openid"
"email"
"profile"
];
preferShortUsername = true;
};
}

View file

@ -1,33 +1,91 @@
{ config, pkgs, lib, ... }: { inputs, config, pkgs, lib, ... }:
let let
svcDomain = "id.${config.networking.domain}"; homelabDomain = inputs.nixos-secrets.homelabDomain;
caddyCertsRoot = "${config.services.caddy.dataDir}/.local/share/caddy/certificates"; svcDomain = "id.${homelabDomain}";
caddyCertsDir = "${caddyCertsRoot}/acme-v02.api.letsencrypt.org-directory"; kanidmCertDir = "/var/lib/kanidm/certs";
certsDir = "/var/lib/kanidm/certs"; caddyCertStore = "${config.services.caddy.dataDir}/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/${svcDomain}";
#kcertloc = "${caddyCertsStore}/${svcDomain}/";
certRenewalScript = pkgs.writeShellScript "copy-kanidm-cert-hook" ''
set -Eeuo pipefail
mkdir -p ${kanidmCertDir}
cp ${caddyCertStore}/${svcDomain}.crt ${kanidmCertDir}/cert.pem
cp ${caddyCertStore}/${svcDomain}.key ${kanidmCertDir}/key.pem
chown kanidm:kanidm ${kanidmCertDir}/*.pem
${pkgs.systemd}/bin/systemctl restart kanidm.service
'';
kanidmCertCopier = "kanidm-cert-copier";
in in
{ {
# NOTE: Domains are serious when they are the root of identity/authnz.
# Recommendation from Kanidm docs for "Maximum" security is to maintain
# Both `example.com` and `id.example-auth.com`, the latter for idm infra exclusively.
# I consider that to be untenable and even more risky.
# The next recommendation is to follow a pattern like so
# id.example.com
# australia.id.example.com
# id-test.example.com
# australia.id-test.example.com
# Example of yoinking certs from caddy: # Example of yoinking certs from caddy:
# https://github.com/marcusramberg/nix-config/blob/e558914dd3705150511c5ef76278fc50bb4604f3/nixos/kanidm.nix#L3 # https://github.com/marcusramberg/nix-config/blob/e558914dd3705150511c5ef76278fc50bb4604f3/nixos/kanidm.nix#L3
# TODO: If possible, consider specifying the cert location here instead of the following kludge. # TODO: If possible, consider specifying the cert location here instead of the following kludge.
services.caddy.virtualHosts."${svcDomain}".extraConfig = '' services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :8443 { reverse_proxy :8443 {
header_up Host {host}
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
transport http { transport http {
tls_server_name ${svcDomain} tls_server_name ${svcDomain}
} }
} }
''; '';
# NOTE: Attempted kludge due to caddy generating (and therefore owning the certs) # NOTE: Cleanup old rules
# systemd.tmpfiles.rules = lib.filter(rule: ! (lib.strings.hasPrefix "C ${kanidmCertDir}" rule)) config.systemd.tmpfiles.rules;
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules = [
"d ${certsDir} 0750 kanidm caddy -" "d ${kanidmCertDir} 0750 kanidm kanidm -"
"C ${certsDir}/cert.pem - kanidm - - ${caddyCertsDir}/${svcDomain}/${svcDomain}.crt"
"C ${certsDir}/key.key - kanidm - - ${caddyCertsDir}/${svcDomain}/${svcDomain}.key"
]; ];
systemd.services.kanidm = { # NOTE: Include automation for copying cert files on renewal.
after = [ "systemd-tmpfiles-setup.service" ]; # systemd.services.caddy.serviceConfig = {
requires = [ "caddy.service" "systemd-tmpfiles-setup.service" ]; # ExecStartPost = [
# "${certRenewalScript}/bin/copy-kanidm-cert-hook"
# ];
# ExecReload = [
# "${pkgs.caddy}/bin/caddy reload --config ${config.services.caddy.configFile}"
# "${certRenewalScript}/bin/copy-kanidm-cert-hook"
# ];
# };
systemd.services.${kanidmCertCopier} = {
description = "Copy Caddy certificates for Kanidm";
requires = [ "caddy.service" ];
after = [ "caddy.service" ];
serviceConfig = {
Type = "oneshot";
User = "root";
ExecStart = "${certRenewalScript}";
}; };
};
# systemd.services.caddy.wantedBy = [ "multi-user.target" ];
# systemd.services.caddy.wants = [ kanidmCertCopier ];
systemd.services.caddy.reloadTriggers = [ kanidmCertCopier ];
systemd.timers.kanidm-cert-copier-daily = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min";
OnCalendar = "daily";
Unit = kanidmCertCopier;
};
};
# systemd.services.kanidm = {
# after = [ kanidmCertCopier ];
# requires = [ kanidmCertCopier ];
# };
users.users.kanidm.extraGroups = [ users.users.kanidm.extraGroups = [
"caddy" "caddy"
]; ];
@ -43,21 +101,25 @@ in
}; };
}; };
services.kanidm = { services.kanidm = {
package = pkgs.kanidmWithSecretProvisioning; package = pkgs.kanidmWithSecretProvisioning_1_7;
enableServer = true; enableServer = true;
serverSettings = { serverSettings = {
# NOTE: Required to start the server: https://kanidm.github.io/kanidm/stable/server_configuration.html # NOTE: Required to start the server: https://kanidm.github.io/kanidm/stable/server_configuration.html
# domain, origin, tls_chain, tls_key # domain, origin, tls_chain, tls_key
domain = svcDomain; domain = svcDomain;
origin = "https://${svcDomain}"; origin = "https://${svcDomain}";
tls_chain = "${certsDir}/cert.pem"; tls_chain = "${kanidmCertDir}/cert.pem";
tls_key = "${certsDir}/key.key"; tls_key = "${kanidmCertDir}/key.pem";
# tls_chain = "${caddyCertStore}/${svcDomain}.crt";
# tls_key = "${caddyCertStore}/${svcDomain}.key";
# NOTE: Optional Settings # NOTE: Optional Settings
# TODO: Configure the rest of the binding properly, should be 363 and maybe 8443
ldapbindaddress = "127.0.0.1:3636"; # For Jellyfin LDAP integration. ldapbindaddress = "127.0.0.1:3636"; # For Jellyfin LDAP integration.
# trust_x_forwarded_for = true; #trust_x_forwarded_for = true;
}; };
enableClient = true; enableClient = true;
@ -74,6 +136,7 @@ in
home_alias = "name"; home_alias = "name";
}; };
# TODO: Migrate the secrets from here to `nixos-secrets`
# NOTE: There are manual steps required as root to allow a user to set # NOTE: There are manual steps required as root to allow a user to set
# their own credentials, or to confiugre an account as posix. As-is this # their own credentials, or to confiugre an account as posix. As-is this
# module doesn't support provisioning a complete user /w credentials. # module doesn't support provisioning a complete user /w credentials.
@ -82,7 +145,9 @@ in
# https://kanidm.github.io/kanidm/stable/accounts/authentication_and_credentials.html#onboarding-a-new-person--resetting-credentials # https://kanidm.github.io/kanidm/stable/accounts/authentication_and_credentials.html#onboarding-a-new-person--resetting-credentials
provision = { provision = {
enable = true; enable = true;
autoRemove = false; autoRemove = true;
acceptInvalidCerts = true;
adminPasswordFile = config.sops.secrets."kanidm/admin-password".path; adminPasswordFile = config.sops.secrets."kanidm/admin-password".path;
idmAdminPasswordFile = config.sops.secrets."kanidm/idm-admin-password".path; idmAdminPasswordFile = config.sops.secrets."kanidm/idm-admin-password".path;
@ -98,6 +163,8 @@ in
"git.users" "git.users"
"git.admins" "git.admins"
"tv.users" "tv.users"
"immich.users"
"miniflux.users"
]; ];
}; };
}; };
@ -107,6 +174,8 @@ in
"git.admins" = {}; "git.admins" = {};
"tv.users" = {}; "tv.users" = {};
"tv.admins" = {}; "tv.admins" = {};
"immich.users" = {};
"miniflux.users" = {};
}; };
}; };
}; };

View file

@ -0,0 +1,79 @@
{ config, pkgs, ... }:
let
homelabDomain = config.networking.domain;
svcDomain = "feeds.${homelabDomain}";
svcPort = "8081"; # Prevent a Conflict
in
{
services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${svcPort}
'';
# NOTE: Ensure the user exists ahead of trying to give secret permissions to that user.
users.users.miniflux = {
isSystemUser = true;
group = "miniflux";
createHome = false;
};
users.groups.miniflux = {};
sops.secrets.miniflux_env = {
sopsFile = ../../secrets/miniflux_admin_credentials.env;
format = "dotenv";
mode = "0440";
owner = "miniflux";
group = "miniflux";
restartUnits = [ "miniflux.service" ];
};
sops.secrets."miniflux/oauth2_client_secret" = {
owner = "miniflux";
group = "kanidm";
mode = "0440";
restartUnits = [ "miniflux.service" "kanidm.service" ];
};
#services.kanidm.provision = {
#groups = {};
#systems.oauth2.miniflux = {
#displayName = "Miniflux Feed Reader";
#originUrl = "https://${fqdn}/callback";
#public = true; # enforces PKCE
#preferShortUsername = true;
#scopeMaps.pages_users = ["openid" "email" "profile"];
#claimMaps."${permissionsMap}".valuesByGroup.pages_admin = ["admin"];
#};
#};
# NOTE: Currently requires some web-interface configuration
services.miniflux = {
enable = true;
adminCredentialsFile = config.sops.secrets.miniflux_env.path;
config = {
BASE_URL = "https://${svcDomain}";
#CREATE_ADMIN = 0;
#DISABLE_LOCAL_AUTH = 1;
OAUTH2_PROVIDER = "oidc";
OAUTH2_CLIENT_ID = "miniflux";
OAUTH2_CLIENT_SECRET_FILE = config.sops.secrets."miniflux/oauth2_client_secret".path;
OAUTH2_REDIRECT_URL = "https://${svcDomain}/oauth2/oidc/callback";
OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://id.${homelabDomain}/oauth2/openid/miniflux";
#OAUTH2_USER_CREATION = 1;
CLEANUP_FREQUENCY = 48;
LISTEN_ADDR = "localhost:${svcPort}";
};
};
services.kanidm.provision.systems.oauth2.miniflux = {
displayName = "miniflux";
originUrl = "https://${svcDomain}/oauth2/oidc/callback";
originLanding = "https://${svcDomain}/";
basicSecretFile = config.sops.secrets."miniflux/oauth2_client_secret".path;
scopeMaps."miniflux.users" = [
"openid"
"email"
"profile"
"groups"
];
# WARNING: PKCE is currently not supported by gitea/forgejo,
# see https://github.com/go-gitea/gitea/issues/21376
allowInsecureClientDisablePkce = true;
preferShortUsername = true;
};
}

View file

@ -0,0 +1,14 @@
# hosts/lithium/services/monitoring
This is a Grafana/Prometheus Monitoring Stack.
Why? Basically for the sake of it.
## Diagram
```mermaid
````
## References
- https://gist.github.com/rickhull/895b0cb38fdd537c1078a858cf15d63e
- https://xeiaso.net/blog/prometheus-grafana-loki-nixos-2020-11-20/

View file

@ -0,0 +1,6 @@
{ ... }: {
imports = [
./grafana.nix
./prometheus.nix
];
}

View file

@ -0,0 +1,26 @@
{ inputs, config, pkgs, ... }:
let
homelabDomain = inputs.nixos-secrets.homelabDomain;
#svcDomain = "grafana.${config.networking.domain}";
svcDomain = "grafana.${homelabDomain}";
svcPort = config.services.grafana.settings.server.http_port;
in
{
services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${toString svcPort}
'';
services.grafana = {
enable = true;
settings = {
server = {
http_addr = "127.0.0.1";
http_port = 3001;
enforce_domain = true;
enable_gzip = true;
domain = svcDomain;
};
analytics.reporting_enabled = false; # NOTE: Disable Telemetry
};
};
}

View file

@ -0,0 +1,4 @@
auth_enabled: false
server:
http_listen_port: 3100

View file

@ -0,0 +1,15 @@
{ ... }:
{
services.loki = {
enable = true;
#configFile = "./loki-local-config.yaml";
# Nix Object representing the data that might otherwise be in a YAML config
# https://github.com/grafana/loki/blob/main/cmd/loki/loki-local-config.yaml
configuration = {
auth_enabled = false;
server = {
};
};
};
}

View file

@ -0,0 +1,36 @@
{ config, pkgs, ... }:
let
#svcDomain = "status.${config.networking.domain}";
svcPort = config.services.prometheus.exporters.node.port;
in
{
#services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
#reverse_proxy :${svcPort}
#'';
services.prometheus = {
enable = true;
port = 9090;
#globalConfig.scrape_interval = "10s"; # "1m"
exporters = {
# Export data about this host
node = {
enable = true;
enabledCollectors = [ "systemd" ];
port = 9091;
};
};
# Read data from the export
scrapeConfigs = [
{
job_name = "node-lithium";
static_configs = [{
targets = [ "localhost:${toString svcPort}" ];
}];
}
];
};
}

View file

@ -0,0 +1,79 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.kanidm;
authDomain = "auth.${config.networking.domain}";
certsDir = config.security.acme.certs."${authDomain}".directory;
in
{
# TODO: Pull in the appropriate sops-nix secrets and get this baby rolling.
# https://github.com/search?q=language%3ANix+services.kanidm&type=code
services.kanidm = {
# NOTE: Pin a specific kanidm version, we don't want issues from auto-updating.
package = pkgs.kanidm_1_6;
enableServer = true;
# TODO: Initial kanidm setup.
# I sort of want users to be able to create their own accounts and what I
# don't want is for any of their account information to be leaked here as
# it can be used for remote logins.
# So kanidm accounts aside from the administration will be "impure".
# I vastly prefer people being able to set their own credentials:
# https://kanidm.github.io/kanidm/stable/accounts/authentication_and_credentials.html#onboarding-a-new-person--resetting-credentials
provision = {
enable = true;
autoRemove = false;
# TODO: Add secrets from `sops-nix`.
adminPasswordFile = "TODO!SETME";
idmAdminPasswordFile = "TODO!SETME";
persons = {
# https://kanidm.github.io/kanidm/stable/accounts/authentication_and_credentials.html#resetting-person-account-credentials
# Needs to be a member of idm_people_admins and idm_high_privilege to prevent idm_service_desk from tampering.
zenware = {
displayName = "zenware";
legalName = "zenware";
mailAddresses = [ "zenware@${config.networking.domain} "];
groups = [
"idm_high_privilege"
"git.users"
"git.admins"
];
};
# TODO: Make an idm_service_desk account.
};
groups = {
# This group is `git` because it could be forgejo, gitea, etc.
"git.users" = {};
"git.admins" = {};
};
systems.oauth2 = {
forgejo = {
displayName = "forgejo";
originUrl = "TODO!SETME";
originLanding = "TODO!SETME";
basicSecretFile = "TODO!SETME";
scopeMaps."git.users" = [
"openid"
"email"
"profile"
];
# WARNING: PKCE is currently not supported by gitea/forgejo,
# see https://github.com/go-gitea/gitea/issues/21376
allowInsecureClientDisablePkce = true;
preferShortUsername = true;
claimMaps.groups = {
joinType = "array";
valuesByGroup."git.admins" = [ "admin" ];
};
};
};
};
#enableClient = false;
clientSettings = {
uri = "https://${authDomain}";
verify_hostnames = true;
verify_ca = true;
};
};
}

View file

@ -0,0 +1,2 @@
[/Script/Pal.PalGameWorldSettings]
OptionSettings=(Difficulty=None,RandomizerType=None,RandomizerSeed="",bIsRandomizerPalLevelRandom=False,DayTimeSpeedRate=1.000000,NightTimeSpeedRate=1.000000,ExpRate=1.000000,PalCaptureRate=1.100000,PalSpawnNumRate=1.100000,PalDamageRateAttack=1.000000,PalDamageRateDefense=1.000000,PlayerDamageRateAttack=1.000000,PlayerDamageRateDefense=1.000000,PlayerStomachDecreaceRate=0.800000,PlayerStaminaDecreaceRate=0.900000,PlayerAutoHPRegeneRate=1.100000,PlayerAutoHpRegeneRateInSleep=1.200000,PalStomachDecreaceRate=0.900000,PalStaminaDecreaceRate=1.000000,PalAutoHPRegeneRate=1.000000,PalAutoHpRegeneRateInSleep=1.000000,BuildObjectHpRate=1.000000,BuildObjectDamageRate=1.000000,BuildObjectDeteriorationDamageRate=1.000000,CollectionDropRate=1.000000,CollectionObjectHpRate=1.000000,CollectionObjectRespawnSpeedRate=1.000000,EnemyDropItemRate=1.000000,DeathPenalty=1,bEnablePlayerToPlayerDamage=False,bEnableFriendlyFire=False,bEnableInvaderEnemy=True,bActiveUNKO=False,bEnableAimAssistPad=True,bEnableAimAssistKeyboard=True,DropItemMaxNum=3000,DropItemMaxNum_UNKO=100,BaseCampMaxNum=128,BaseCampWorkerMaxNum=15,DropItemAliveMaxHours=1.000000,bAutoResetGuildNoOnlinePlayers=False,AutoResetGuildTimeNoOnlinePlayers=72.000000,GuildPlayerMaxNum=20,BaseCampMaxNumInGuild=4,PalEggDefaultHatchingTime=4.500000,WorkSpeedRate=1.100000,AutoSaveSpan=30.000000,bIsMultiplay=False,bIsPvP=False,bHardcore=False,bPalLost=False,bCharacterRecreateInHardcore=False,bCanPickupOtherGuildDeathPenaltyDrop=False,bEnableNonLoginPenalty=True,bEnableFastTravel=True,bIsStartLocationSelectByMap=True,bExistPlayerAfterLogout=False,bEnableDefenseOtherGuildPlayer=False,bInvisibleOtherGuildBaseCampAreaFX=False,bBuildAreaLimit=False,ItemWeightRate=1.000000,CoopPlayerMaxNum=4,ServerPlayerMaxNum=32,ServerName="GameNight Pals",ServerDescription="",AdminPassword="PenQueen",ServerPassword="penking",PublicPort=8211,PublicIP="",RCONEnabled=False,RCONPort=25575,Region="",bUseAuth=True,BanListURL="https://api.palworldgame.com/api/banlist.txt",RESTAPIEnabled=False,RESTAPIPort=8212,bShowPlayerList=False,ChatPostLimitPerMinute=30,CrossplayPlatforms=(Steam,Xbox,PS5,Mac),bIsUseBackupSaveData=True,LogFormatType=Text,SupplyDropSpan=90,EnablePredatorBossPal=True,MaxBuildingLimitNum=0,ServerReplicatePawnCullDistance=15000.000000,bAllowGlobalPalboxExport=True,bAllowGlobalPalboxImport=False,EquipmentDurabilityDamageRate=1.000000,ItemContainerForceMarkDirtyInterval=1.000000)

View file

@ -0,0 +1,53 @@
{ config, pkgs, lib, ... }:
let
palworldSettings = builtins.readFile ./PalWorldSettings.ini;
in
{
# NOTE: Partly inspired by: https://github.com/pocketpairjp/palworld-dedicated-server-docker
networking.firewall.allowedUDPPorts = [ 8211 ];
users.users.palworld = {
isSystemUser = true;
createHome = true;
home = "/home/palworld";
homeMode = "750";
group = "palworld";
extraGroups = [ "steamcmd" ];
};
users.groups.palworld = {};
systemd.services.palworld = {
enable = true;
description = "PalWorld Dedicated Server";
#after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStartPre = ''
${pkgs.steamcmd}/bin/steamcmd +login anonymous \
+app_update 2394010 validate +quit
'';
ExecStart = ''
${pkgs.steam-run}/bin/steam-run \
.steam/steam/Steamapps/common/PalServer/PalServer.sh -publiclobby \
-useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS
'';
WorkingDirectory = "/home/palworld";
Restart = "always";
RuntimeMaxSec = "1d"; # NOTE: This thing has memory leaks, restart to save our selves.
User = "palworld";
};
};
# NOTE: Config is stashed at the following directory.
# /home/palworld/.steam/steam/Steamapps/common/PalServer/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini
# TODO: There are benefits to including the meat of the configuration inside the 'nix' file.
# Namely that it will result in actually updating the config when I rebuild.
environment.etc."palworld/PalWorldSettings.ini" = {
target = "/home/palworld/.steam/steam/Steamapps/common/PalServer/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini";
text = palworldSettings;
mode = "0644";
user = "palworld";
group = "palworld";
};
}

View file

@ -0,0 +1,39 @@
{ config, ... }:
{
services.smartd = {
enable = true;
devices = [
{
device = "/dev/disk/by-id/ata-CT500MX500SSD1_2206E607D6AA";
}
{
device = "/dev/disk/by-id/ata-CT500MX500SSD1_2206E607D728";
}
{
device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2B73HT";
}
{
device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2PSELL";
}
{
device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2B4RSM";
}
{
device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL23XYMM";
}
{
device = "/dev/disk/by-id/nvme-Samsung_SSD_960_EVO_500GB_S3X4NB0K244331X";
}
{
device = "/dev/disk/by-id/nvme-Samsung_SSD_960_EVO_500GB_S3X4NB0K244303V";
}
];
};
services.prometheus.exporters.smartctl = {
enable = config.services.smartd.enable;
openFirewall = config.services.smartd.enable;
# https://github.com/prometheus-community/smartctl_exporter?tab=readme-ov-file#why-is-root-required-cant-i-add-a-user-to-the-disk-group
user = "root";
};
}

View file

@ -13,6 +13,8 @@ in
enable = true; enable = true;
# NOTE: NixOS Attributes here resolve into these ENV vars: # NOTE: NixOS Attributes here resolve into these ENV vars:
# https://github.com/louislam/uptime-kuma/wiki/Environment-Variables # https://github.com/louislam/uptime-kuma/wiki/Environment-Variables
# settings = {}; settings = {
PORT = "4000";
};
}; };
} }

15
hosts/lithium/sops.nix Normal file
View file

@ -0,0 +1,15 @@
{ inputs, ... }:
let
secretsPath = builtins.toString inputs.nixos-secrets;
in
{
#imports = [ inputs.sops-nix.nixosModules.sops ];
sops = {
#defaultSopsFile = "${secretsPath}/${config.hostname}/secrets.yaml";
#defaultSopsFile = "${secretsPath}/global/secrets.yaml";
# TODO: Make this test the hostname.
#defaultSopsFile = "${secretsPath}/lithium/secrets/common.yaml";
defaultSopsFile = "${secretsPath}/lithium/secrets.yaml";
};
}

View file

@ -10,6 +10,9 @@
signal-desktop signal-desktop
obs-studio obs-studio
]; ];
services.udev.extraRules = ''
SUBSYSTEM=="usb", ATTRS{idVendor}=="14ed", ATTRS{idProduct}=="1012", MODE="0666", GROUP="audio"
'';
# Hardware Specific programs... # Hardware Specific programs...
#programs.ryzen-monitor-ng.enable = true; #programs.ryzen-monitor-ng.enable = true;
#programs.rog-control-center.enable = true; #programs.rog-control-center.enable = true;

View file

@ -7,11 +7,11 @@
../../modules/nixos/desktop.nix ../../modules/nixos/desktop.nix
../../modules/nixos/gaming.nix ../../modules/nixos/gaming.nix
inputs.nixos-hardware.nixosModules.asus-rog-strix-x570e inputs.nixos-hardware.nixosModules.asus-rog-strix-x570e
#./hardware.nix ./hardware.nix
./configuration.nix ./configuration.nix
./nvidia.nix ./nvidia.nix
./secure-boot.nix ./secure-boot.nix
inputs.disko.nixosModules.disko #inputs.disko.nixosModules.disko
./disko.nix #./disko.nix
]; ];
} }

View file

@ -33,6 +33,7 @@
in in
nixpkgs.lib.nixosSystem { nixpkgs.lib.nixosSystem {
inherit system; inherit system;
specialArgs = { inherit inputs hostname; };
modules = [ hostModule ] modules = [ hostModule ]
++ userModules ++ userModules
++ extraModules ++ extraModules

37
localnotes.md Normal file
View file

@ -0,0 +1,37 @@
Run disko first, then install.
```bash
sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko -- --mode disko /path/to/disko.nix
sudo nixos-install --root /mnt --no-root-passwd --flake .#titanium
```
need to comment out lanzaboote stuff to get it working... :(
then re-enable it after the system is online again.
There is clearly some kind of quirk about boot drives.
We're going to try again, and double-check reading through config this time.
I've already partitioned the drive(s) with disko, but I don't have a reason not to do it again.
There is some reason why if I import disko into my nixos config it creates a conflict between the name of the drives.
```bash
/dev/crypt/mapper
# and a full uuid path
```
I figured it out, after you have the disk paths and stuff setup with `disko` you no longer need to maintain them in `hardware-configuration.nix`
Which.. is actually quite nice to be honest.
## Some Ideas to consider
- https://github.com/Sveske-Juice/declarative-jellyfin
- https://docs.clan.lol
- fido2 luks cryptenroll
- https://discourse.nixos.org/t/using-fido2-luks-with-yubikey-pin/15484
- https://github.com/nix-community/nixhelm/

24
localsecrets.yaml Normal file
View file

@ -0,0 +1,24 @@
cloudflare_api_token: ENC[AES256_GCM,data:IqZ2LkBUyboO9494G3LPFn8prx48SDFluz6u4KDzQfQxTyqiVkORRQ==,iv:5iSnHMFHOEXDxt5/fSQ/83OzG2iU6LQ9OCnah5Qnj1A=,tag:zBQvE9KvW195mLtwjFKWqQ==,type:str]
cloudflare:
api_token: ENC[AES256_GCM,data:ZkiVQeVFHQ==,iv:402yjlUkamfzgAqMQCChR+ui4nsA625GidVIwCOc8bM=,tag:xErz+vXV8en2c3WdHfqUsg==,type:str]
email: ENC[AES256_GCM,data:AnI4i1ar70FvyLzVqiElJTbZ6V12Tw==,iv:VYoK2Q2XmleP2SV9Axy8JPTrPeLjnJBkhaXVuCWGUtA=,tag:uxB9JQJWUwoIQ1QtpbvT8Q==,type:str]
env:
domain_name: ENC[AES256_GCM,data:sGpnauqZ2LSY+Hf34eA=,iv:V1OOAo9zoEAkXDLL2la6DF+FoCj4Lzqg88dbHHSqcXs=,tag:C+0yaNo/TwAtym7NSxNnyA==,type:str]
yubico:
id: ENC[AES256_GCM,data:n80pw3A=,iv:pS/T3JGbVQkmYJ6EnwcdITe9J8knVBJiEn8njxPzAcM=,tag:gwg5pl6ugfBH4qwpcr1cjA==,type:int]
secret_key: ENC[AES256_GCM,data:kax8+XHJwpqFtRdKpkRR5sFhsKawlT7Ru92Umw==,iv:wSs4wdAkHyacKUmIxGfn8KDv1A8yW6E+0eBuXnOzhMA=,tag:Y6Om1OShD6s3dBALseN/qQ==,type:str]
tailscale:
auth_key: ENC[AES256_GCM,data:lIT80TrYfqMa8k6wsxC5BV6sBg6PCw5+AJaUtMhfJuXTIOz48PZKpCFq5z76TenpGpYT1vdqfCGd8Z1UfA==,iv:5LCWydaBX0ap5VScmjSm/QLtkeYy7u0ImVXoaY8URZs=,tag:dZV4s026eS4ME68ET+JxGw==,type:str]
vrising_server_name: ENC[AES256_GCM,data:guRSn4xsGFJ0,iv:spCO2OuFr2JmFL+qboq2dwrpxb7KufxtscZI8/3r9SQ=,tag:qbFV4IIiFmEQW72So6pm7A==,type:str]
valheim_server_name: ENC[AES256_GCM,data:PJV694YqSnOkBw==,iv:yIjgunzkiVdO1pUuAOtKcRI2zhBSb2sfDfM5WrPsVa8=,tag:I18ugPusdBoADlSxYzIYNw==,type:str]
kanidm:
bind_password: ENC[AES256_GCM,data:l9nP,iv:Qtk6hGj+RnLfsNBRp4QHpCo/Oxq+EdVu62xmClPbPNk=,tag:7PRHrMT5kJdtqV2xXZPzZg==,type:int]
tailscale:
auth_key: ENC[AES256_GCM,data:RFno,iv:wG8XyXMQt/BLjp3jW0j509cDmF/V+UqGAFCjJ3twQ3k=,tag:5z1krKBPDWlZGvHleS4rPA==,type:int]
gitea:
secret_key: ENC[AES256_GCM,data:u5EV,iv:UdQaFFC2Rh0aDbqzHdowhTmxrKbOhWEmGmUEjkOUrg4=,tag:MfB6Jbc3IPc8h4VAd4urEg==,type:int]
internal_token: ENC[AES256_GCM,data:K1tX,iv:MhM1IURTet1e919eI9NpitEdfG8WRB33E4GsCmcSDro=,tag:146dEI1/Bj+w5Ul+Sbt9bw==,type:int]
db_password: ENC[AES256_GCM,data:muy+,iv:e0drAD03om5xAmLOUa4IxTFHQ5U1xeK+fgDOhATnMU8=,tag:JVorF+C4+ENhFZ7/0QSwrQ==,type:int]
oauth_client_secret: ENC[AES256_GCM,data:hVM95DQxfS9Af/z+C5AgjpcoHBs=,iv:pq4lXNYXzFIaNUWhmCV5EwD4icjziCWVqpSuIg1FzZQ=,tag:z/lxukCKwgriNLqqeHDFuQ==,type:str]
jellyfin:
oauth_client_secret: ENC[AES256_GCM,data:IQI6FfOga+3bmf+B+/vpGao=,iv:lC/zbax5mgzQBRNIIbeviWBXtMKSegKFJlHdY+bCSyo=,tag:g0BUqfHZrJB2gJUIjLjnuA==,type:str]

View file

@ -5,7 +5,8 @@
xdg-desktop-portal-gtk xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland xdg-desktop-portal-hyprland
xwayland xwayland
rofi-wayland #rofi-wayland
rofi
waybar waybar
hyprpaper hyprpaper
kitty # hyprland default term kitty # hyprland default term

View file

@ -0,0 +1,38 @@
{
lib,
stdenv,
fetchFromGitHub,
}:
# Based on:
# https://github.com/NixOS/nixpkgs/blob/nixos-25.05/pkgs/by-name/zs/zsa-udev-rules/package.nix
stdenv.mkDerivation {
pname = "mv7-udev-rules";
version = "unstable-2025-06-20";
src = fetchFromGitHub {
owner = "zenware";
repo = "mv7-udev-rules";
rev = "000000000000000000000000000000000";
hash = "sha256-0000000000000000000000000";
};
# Only copy udev rules
dontConfigure = true;
dontBuild = true;
dontFixup = true;
installPhase = ''
mkdir -p $out/lib/udev/rules.d
cp dist/linux64/99-mv7-mic.rules $out/lib/udev/rules.d/
'';
meta = with lib; {
description = "udev rules for MV7 devices";
license = licenses.mit;
maintainers = with maintainers; [ zenware ];
platforms = platforms.linux;
homepage = "https://github.com/zenware/mv7-udev-rules";
};
}

36
secrets/global.yaml Normal file
View file

@ -0,0 +1,36 @@
cloudflare_api_token: ENC[AES256_GCM,data:IqZ2LkBUyboO9494G3LPFn8prx48SDFluz6u4KDzQfQxTyqiVkORRQ==,iv:5iSnHMFHOEXDxt5/fSQ/83OzG2iU6LQ9OCnah5Qnj1A=,tag:zBQvE9KvW195mLtwjFKWqQ==,type:str]
kanidm:
bind_password: ENC[AES256_GCM,data:l9nP,iv:Qtk6hGj+RnLfsNBRp4QHpCo/Oxq+EdVu62xmClPbPNk=,tag:7PRHrMT5kJdtqV2xXZPzZg==,type:int]
tailscale:
auth_key: ENC[AES256_GCM,data:RFno,iv:wG8XyXMQt/BLjp3jW0j509cDmF/V+UqGAFCjJ3twQ3k=,tag:5z1krKBPDWlZGvHleS4rPA==,type:int]
gitea:
secret_key: ENC[AES256_GCM,data:u5EV,iv:UdQaFFC2Rh0aDbqzHdowhTmxrKbOhWEmGmUEjkOUrg4=,tag:MfB6Jbc3IPc8h4VAd4urEg==,type:int]
internal_token: ENC[AES256_GCM,data:K1tX,iv:MhM1IURTet1e919eI9NpitEdfG8WRB33E4GsCmcSDro=,tag:146dEI1/Bj+w5Ul+Sbt9bw==,type:int]
db_password: ENC[AES256_GCM,data:muy+,iv:e0drAD03om5xAmLOUa4IxTFHQ5U1xeK+fgDOhATnMU8=,tag:JVorF+C4+ENhFZ7/0QSwrQ==,type:int]
oauth_client_secret: ENC[AES256_GCM,data:hVM95DQxfS9Af/z+C5AgjpcoHBs=,iv:pq4lXNYXzFIaNUWhmCV5EwD4icjziCWVqpSuIg1FzZQ=,tag:z/lxukCKwgriNLqqeHDFuQ==,type:str]
jellyfin:
oauth_client_secret: ENC[AES256_GCM,data:IQI6FfOga+3bmf+B+/vpGao=,iv:lC/zbax5mgzQBRNIIbeviWBXtMKSegKFJlHdY+bCSyo=,tag:g0BUqfHZrJB2gJUIjLjnuA==,type:str]
sops:
age:
- recipient: age1mv8xtvkuuw3hphq5ytaekz7p8a4kht79uajyhy534uy9e5472fhqj5zpxu
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuOTRKeDNjSEdnQ2ZRNnI3
dzZJc0xJdFFOUk9tWDJRcHZZSXVUMkpRQUc0CjhGWkNYR1daR2VWT2JOQUFZVlJO
ZGswSU5vaDJLWm5INXp5MlVQYjBCUzQKLS0tIDlrZjN6SGFnNDY4TEN6NjlPN01Y
dGRsTVpBMGVaVytpTm55WEIzNVpHTDQK8p9yexr9L7TwBPu/jKZkdDD2GOt9+nkK
6tcckBjVx/SUIs7U0qbE6DkzCXIWo2Ce5hsk4RE1p91Y4vocaMPxYA==
-----END AGE ENCRYPTED FILE-----
- recipient: age148yre4vaxp6lm59rft24te46szawqyguf8znkrtpq7ud8tpteauqxkwyjl
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyZUI5VFp5R1Z1SU5XSEtt
QndNajRsR0VyOTUrNjljcnlVRW8vRnVid1hjCmhraVA1anFEdUJoa0lMNm5RcjFE
b1o3bzBpRm83VVhWQy94NXNqSjJSekUKLS0tIGNaQno3RzEyYlNPT0RsNzEzaUd6
c2tIcEx4VGpIVXZzcEJoUTRGRjdSTjAKk4C6PQiAZpunDmNitkrTBq6PqRe53mZB
ehF6fL7DTveVwWq02csekQdi7hXShkoH7EEMw1LrEe6IP7oJbvZ3jw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-04T13:43:02Z"
mac: ENC[AES256_GCM,data:Kd+9kPRgwfdJh9rMADopqJyocPg4M3hnvW8LYxV8jcoOGvuK+rBPu0rQF30ZGUq0i7LG7WiqN+m7nDTrl6KcO4WN4IyTqLJ/lheOLiBTpbUv68VEYPEbTEkvTMrfhsaCNk3AUuUI34cle+k9Q0/QgNenVx7yH77VzR3aiYH/hzA=,iv:s6+V8wTYTE6Q28Kyjb2NlyoGRvUaliaeJAn6htQ1Xwo=,tag:ZkO/mKZ/Dk1jit6qgTxzMA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2

56
secrets/temp.yaml Normal file
View file

@ -0,0 +1,56 @@
#ENC[AES256_GCM,data:dqhy221XOImoFPMsVz3eFipnQZRxDvoXAWRW4gT/UKqmJrsZp/CgmISo/yx7d3Y+Otk2mZHPmi8=,iv:jPJ3jL7heD721vNnGveNUNdOOwJngCFFbaKHxQxwu1w=,tag:EX/SqAJa9H1KMUnJHvwBbw==,type:comment]
#ENC[AES256_GCM,data:2QAMIK1AD8iEVsBl/tEiWAAjjpMzjvnVpoIa8h60KSCEsU8nycqndOPCLq3BOJGK0a5qITm0,iv:HOJ0tFMEY+99aDXdWjiHLXhpssSEMuMhby/n2W/0HqA=,tag:bEZ+IruPQYNj8rJXn3kbvA==,type:comment]
cloudflare:
api_token: ENC[AES256_GCM,data:ZkiVQeVFHQ==,iv:402yjlUkamfzgAqMQCChR+ui4nsA625GidVIwCOc8bM=,tag:xErz+vXV8en2c3WdHfqUsg==,type:str]
email: ENC[AES256_GCM,data:AnI4i1ar70FvyLzVqiElJTbZ6V12Tw==,iv:VYoK2Q2XmleP2SV9Axy8JPTrPeLjnJBkhaXVuCWGUtA=,tag:uxB9JQJWUwoIQ1QtpbvT8Q==,type:str]
#ENC[AES256_GCM,data:qcSPXWftLd3ojT2kQrqsZFuIzRSk7otMj+DA945UYvrut1O6mjYrzKwf/IyHUmlaDFh7kenPXgGDwm5VU9w=,iv:wRGpS4aeMO9tpF4vHE6sKMt1LA9YcLW8+G7VBWwYMOo=,tag:VEDXurG64aT43pi0lKLN1w==,type:comment]
env:
domain_name: ENC[AES256_GCM,data:sGpnauqZ2LSY+Hf34eA=,iv:V1OOAo9zoEAkXDLL2la6DF+FoCj4Lzqg88dbHHSqcXs=,tag:C+0yaNo/TwAtym7NSxNnyA==,type:str]
#ENC[AES256_GCM,data:IYmlxtKW06u6orlQii4IfB45O88ofznUvuuzYt0=,iv:YdDbHxqG/Nd9CF9l/BBMEO28VYnTiKvkdXxTbMJjRG8=,tag:y0rADtZhmdYZjiax9YvJzg==,type:comment]
yubico:
id: ENC[AES256_GCM,data:n80pw3A=,iv:pS/T3JGbVQkmYJ6EnwcdITe9J8knVBJiEn8njxPzAcM=,tag:gwg5pl6ugfBH4qwpcr1cjA==,type:int]
secret_key: ENC[AES256_GCM,data:kax8+XHJwpqFtRdKpkRR5sFhsKawlT7Ru92Umw==,iv:wSs4wdAkHyacKUmIxGfn8KDv1A8yW6E+0eBuXnOzhMA=,tag:Y6Om1OShD6s3dBALseN/qQ==,type:str]
#ENC[AES256_GCM,data:Zwum1QmpeQUb4PIxdqY=,iv:stOFFRpaR+kHIfADKS/o//K1+oQ+Y9ANNp5LbrbgW9c=,tag:hUdnj2menajZhM7yuRs0aQ==,type:comment]
tailscale:
auth_key: ENC[AES256_GCM,data:lIT80TrYfqMa8k6wsxC5BV6sBg6PCw5+AJaUtMhfJuXTIOz48PZKpCFq5z76TenpGpYT1vdqfCGd8Z1UfA==,iv:5LCWydaBX0ap5VScmjSm/QLtkeYy7u0ImVXoaY8URZs=,tag:dZV4s026eS4ME68ET+JxGw==,type:str]
#ENC[AES256_GCM,data:TBtQO7kI+rfcy2XlpYCIAkviKS0=,iv:JjLLJqDMFmHtrqgIH9xVlJ6gbeFT7g74kFL8nZH+nCk=,tag:rUpKWnstKtqbJbGLk9dQfw==,type:comment]
vrising_server_name: ENC[AES256_GCM,data:guRSn4xsGFJ0,iv:spCO2OuFr2JmFL+qboq2dwrpxb7KufxtscZI8/3r9SQ=,tag:qbFV4IIiFmEQW72So6pm7A==,type:str]
valheim_server_name: ENC[AES256_GCM,data:PJV694YqSnOkBw==,iv:yIjgunzkiVdO1pUuAOtKcRI2zhBSb2sfDfM5WrPsVa8=,tag:I18ugPusdBoADlSxYzIYNw==,type:str]
example_key: ENC[AES256_GCM,data:RPKzGyhQVMt53UIrbg==,iv:WdJEIrcLjqwyaEiXAOGoGihp0tgr3P0j+coQThRSAqM=,tag:efPXuh06+ZEyRAY6M8O6Vg==,type:str]
#ENC[AES256_GCM,data:hwNzDeFecGqtr1gCdxQ2Dg==,iv:fvPKS3tH5i+HdJhGxJz7Bf0nM/DZDqGYFy23/CTQq14=,tag:ieLl2TJuIj0mPzAHlZeWKw==,type:comment]
example_array:
- ENC[AES256_GCM,data:Wh1JeEYh5XIXqxCi/kc=,iv:3MH0nBTXkYWSf2FTSJAelSR0jPHhsp0AEPH3bq1v6WM=,tag:pGs/aD013fzjgX1mzFWSeQ==,type:str]
- ENC[AES256_GCM,data:0mIilx8XdDOg5oRxcrA=,iv:Tm6E3M8hi3HqTxO/vFO3GVlZ8IgUKjAliYbHwh4SEqw=,tag:g9u7zaBENVdcloWpj6jxBg==,type:str]
example_number: ENC[AES256_GCM,data:5M+hN39uq5hn7A==,iv:vxWdPRafSZ4LYKgjDK8hrqwb7mMjLDxf7/mcb9Fe4r8=,tag:YmTtnwCcnX+xVs09XFLQfA==,type:float]
example_booleans:
- ENC[AES256_GCM,data:AtfqPA==,iv:/Hsubc6st3Htaidi634RAndd9Rl2Ldetzm+YfZ2Vu0o=,tag:TqC6nn7cAWffzuWk0e2tCw==,type:bool]
- ENC[AES256_GCM,data:nkbswos=,iv:FBM0yr8MdotAWwxyD6aHhYPjINDeJ5Yu3PRQAg6ey/8=,tag:oCgE9qJHRNLDboO80dOjgw==,type:bool]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1mv8xtvkuuw3hphq5ytaekz7p8a4kht79uajyhy534uy9e5472fhqj5zpxu
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5cGxrQkpqTGJiaGNtenVG
Y2JhVVpDenhuS050SU9hald0RlRRdGpIK3owCkxSNVAzTVNlbmNGSjNZRVlYb3E3
RVM0ZUVmNkJDRW1NcFpzcjR3QndTaGcKLS0tIElUSCsyQmF1cW52UnVsQlZVT1E0
OUNLVk1uNWJNbUNRS1d0Q3FIYXFTZGcKRRK5AO8fQ835zNUDoseBAMPEIxlmAxXT
kv82umpkOyAKQe4opDrxdaJQwoT4nYqdr26ZXCEhajNo1sit9j1z/g==
-----END AGE ENCRYPTED FILE-----
- recipient: age148yre4vaxp6lm59rft24te46szawqyguf8znkrtpq7ud8tpteauqxkwyjl
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmcUVsQU9MNHExd0QyWlZ2
eVVFd1h4WXJyb0lERFFFbWlqQzFGUFJMT3lJCk1iNXRlVTJjaVBjNjB2LzRIcUdr
dk01QU1hbXZwSk50SXhyWlY3WTM4SEEKLS0tIE95eDEydkExVkU1RUNkVUh0TnU4
cGpKVCt3dnVOcTYycDNGeVNFWTQvM28KNbvMhnu/kee2lvEqzTeg7708H+HwxSlD
hR4rWnf/lvA66DIFP7RQN0neouC3mD71irlhAYTWV9h3J9/0z/De1A==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-02T20:31:13Z"
mac: ENC[AES256_GCM,data:YaTn5x0jBvM9Yjj53IClYX11zIYZlrp95IbljXX3E5GJApTL4Mkigv1sPJXwq4jJ67JCQL3bdmLMyvWDfuLd1deVtG1LXD4NFYxFaeUXWgBfPs2IW3l/JakU6kliluZRwe5hwhCNIb/+7t9948HLq5MDIaLU2PDeASK/wySOYno=,iv:3D4o66NrUlxFagO80LSyei8BsKyb6dYN75eHs/MhOpw=,tag:3oNBSmZwGkv8MJduv4Sr8w==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.2

16
users/jay/default.nix Normal file
View file

@ -0,0 +1,16 @@
{ pkgs, lib, ... }:
{
users.users.jay = {
home =
if pkgs.stdenv.isLinux then
lib.mkDefault "/home/jay"
else if pkgs.stdenv.isDarwin then
lib.mkDefault "/Users/jay"
else
abort "Unsupported OS";
} // lib.optionalAttrs pkgs.stdenv.isLinux {
isNormalUser = true;
extraGroups = [ "networkmanager" "wheel" ];
# hashedPassword = "";
};
}

68
users/jay/home.nix Normal file
View file

@ -0,0 +1,68 @@
{ pkgs, lib, ... }:
{
# NOTE: This file contains options that resolve under home-manager.users.<username>
home.stateVersion = "25.05";
home.sessionVariables = {
EDITOR = "hx";
};
home = {
username = "jay";
homeDirectory =
if pkgs.stdenv.isLinux then
lib.mkDefault "/home/jay"
else if pkgs.stdenv.isDarwin then
lib.mkDefault "/Users/jay"
else
abort "Unsupported OS";
};
home.packages = with pkgs; [ ]
# linux only
# TODO: Add a test for linux + desktop environment
++ (lib.optionals pkgs.stdenv.isLinux [
tree
cfspeedtest
ripgrep
helix
nil
])
# linux + desktop manager
#++ (lib.optionals (pkgs.stdenv.isLinux && osConfig.services.desktopManager.enabled != null)
#[
# firefox
#])
# darwin only
++ (lib.optionals pkgs.stdenv.isDarwin [
cfspeedtest
ripgrep
]);
programs.fish.enable = true;
# TODO: Get that working again.
#users.users.jml.shell = pkgs.fish;
programs = {
bat.enable = true;
fzf.enable = true;
jq.enable = true;
btop.enable = true;
};
programs.git = {
enable = true;
userName = "Jay Looney";
userEmail = "jay.m.looney@gmail.com";
aliases = {
ol = "log --oneline";
};
ignores = [ "*~" "*.swp" ];
extraConfig = {
push.default = "simple";
credential.helper = "cache --timeout=7200";
init.defaultBranch = "main";
log.decorate = "full";
log.date = "iso";
merge.conflictStyle = "diff3";
};
};
}