even more backups of things

This commit is contained in:
Jay Looney 2025-10-28 16:11:45 -05:00
parent b8d125d448
commit 630f9b0074
46 changed files with 1166 additions and 197 deletions

View file

@ -113,6 +113,10 @@ nix-shell -p sops --run "sops secrets/example.yaml"
``` ```
## 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 = {};
};
}

88
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": {
@ -119,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": {
@ -167,11 +167,11 @@
"spectrum": "spectrum" "spectrum": "spectrum"
}, },
"locked": { "locked": {
"lastModified": 1750358184, "lastModified": 1758113222,
"narHash": "sha256-17EYMeY5v8KRk9HW6Z4dExY8Wg4y/zM2eM2wbbx+vMs=", "narHash": "sha256-Q5i/qaj6v6F4N1Q5gI/4aL0IEEUE/LjQuwcA8L5IOMc=",
"owner": "astro", "owner": "astro",
"repo": "microvm.nix", "repo": "microvm.nix",
"rev": "fd9f5dba1ffee5ad6f29394b2a9e4c66c1ce77dc", "rev": "b9206e245c07c0782beff58e1e94bb48b2531d15",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -182,11 +182,11 @@
}, },
"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": {
@ -195,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"
} }
@ -227,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": [
@ -261,7 +296,8 @@
"lanzaboote": "lanzaboote", "lanzaboote": "lanzaboote",
"microvm": "microvm", "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"
} }
}, },
@ -293,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": {
@ -309,11 +345,11 @@
"spectrum": { "spectrum": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1746869549, "lastModified": 1754675037,
"narHash": "sha256-BKZ/yZO/qeLKh9YqVkKB6wJiDQJAZNN5rk5NsMImsWs=", "narHash": "sha256-afS08F7lfMUBR4qrBxinN1kuxu+DoHQ5TPNVp9VS/OA=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "d927e78530892ec8ed389e8fae5f38abee00ad87", "rev": "586577f3015397afacd83bc185454f4cc3c8028f",
"revCount": 862, "revCount": 955,
"type": "git", "type": "git",
"url": "https://spectrum-os.org/git/spectrum" "url": "https://spectrum-os.org/git/spectrum"
}, },

View file

@ -13,9 +13,12 @@
disko.inputs.nixpkgs.follows = "nixpkgs"; disko.inputs.nixpkgs.follows = "nixpkgs";
microvm.url = "github:astro/microvm.nix"; microvm.url = "github:astro/microvm.nix";
microvm.inputs.nixpkgs.follows = "nixpkgs"; 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, microvm, ...}: 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;
@ -31,8 +34,12 @@
}; };
lithium = mkSystem { lithium = mkSystem {
hostname = "lithium"; hostname = "lithium";
# extraModules = [ inputs.sops-nix.nixosModules.sops ]; #specialArgs = {inherit inputs;};
extraModules = [ microvm.nixosModules.host ]; 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,6 +1,6 @@
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
{ {
sops.defaultSopsFile = ./secrets/common.yaml; #sops.defaultSopsFile = ./secrets/common.yaml;
networking.hostName = "lithium"; networking.hostName = "lithium";
networking.domain = lib.mkForce config.vars.domain; networking.domain = lib.mkForce config.vars.domain;
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [

View file

@ -16,6 +16,7 @@
./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
@ -25,12 +26,18 @@
./services/miniflux ./services/miniflux
./services/forgejo.nix ./services/forgejo.nix
./services/immich.nix
./services/calibre-web.nix
# Monitoring # Monitoring
./services/monitoring ./services/monitoring
./services/smartd.nix
# Game Servers # Game Servers
./services/palworld ./services/palworld
# TODO: Add Karakeep with yt-dlp + https://news.ycombinator.com/item?id=45595084
# Services running in virtual machines # Services running in virtual machines
#./microvms #./microvms

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,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

@ -1,11 +1,13 @@
{ inputs, config, pkgs, lib, ... }: { inputs, config, pkgs, lib, ... }:
let let
homelabDomain = inputs.nixos-secrets.homelabDomain; homelabDomain = inputs.nixos-secrets.homelabDomain;
certDir = config.security.acme.certs."${homelabDomain}".directory; #certDir = config.security.acme.certs."${homelabDomain}".directory;
svcDomain = "books.${homelabDomain}"; svcDomain = "books.${homelabDomain}";
svcHttpPort = config.services.calibre-web.listen.port; svcHttpPort = config.services.calibre-web.listen.port;
web_data_dir = "calibre-web"; web_data_dir = "calibre-web";
# TODO: I want the actual media stored in the tank.
library_path = "/tank/media/library/books"; library_path = "/tank/media/library/books";
#library_path = "/var/lib/calibre-library";
in in
{ {
# TODO: This isn't the right place for this, but we need to guarantee that a # TODO: This isn't the right place for this, but we need to guarantee that a
@ -14,19 +16,25 @@ in
users.groups.media = {}; users.groups.media = {};
services.caddy.virtualHosts."${svcDomain}".extraConfig = '' services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${toString svcHttpPort} reverse_proxy localhost:8883
encode {
zstd
gzip
minimum_length 1024
}
''; '';
# 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 = { services.calibre-web = {
enable = true; enable = true;
listen.port = 8083; listen.port = 8883;
# NOTE: Don't need to open calibre-web port, it's served by reverse_proxy # NOTE: Don't need to open calibre-web port, it's served by reverse_proxy
openFirewall = false; openFirewall = true; # TODO: Temporarily opened to allow configuration from inside my network.
user = "calibre-web"; user = "calibre-web";
group = "calibre-web"; group = "calibre-web";
@ -38,6 +46,7 @@ in
options = { options = {
enableBookUploading = true; enableBookUploading = true;
enableBookConversion = 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; calibreLibrary = library_path;
}; };
}; };

View file

@ -41,6 +41,7 @@ in
server = { server = {
DOMAIN = svcDomain; DOMAIN = svcDomain;
ROOT_URL = "https://${svcDomain}"; ROOT_URL = "https://${svcDomain}";
HTTP_PORT = 3000;
}; };
# NOTE: Actions support is based on: https://github.com/nektos/act # NOTE: Actions support is based on: https://github.com/nektos/act
#actions = { #actions = {
@ -49,6 +50,7 @@ in
#}; #};
actions.ENABLED = false; actions.ENABLED = false;
# NOTE: Registration is handled with kanidm. # NOTE: Registration is handled with kanidm.
# Registration button link is at /user/sign_up
service = { service = {
REGISTER_EMAIL_CONFIRM = false; REGISTER_EMAIL_CONFIRM = false;
DISABLE_REGISTRATION = false; DISABLE_REGISTRATION = false;
@ -87,13 +89,15 @@ in
services.kanidm.provision.systems.oauth2.forgejo = { services.kanidm.provision.systems.oauth2.forgejo = {
displayName = "forgejo"; displayName = "forgejo";
# TODO: Get this from Forgejo # TODO: Get this from Forgejo
originUrl = "https://git.${homelabDomain}/user/oauth2/${homelabDomain}/callback"; # originUrl = "https://git.${homelabDomain}/user/oauth2/${homelabDomain}/callback";
originUrl = "${config.services.forgejo.settings.server.ROOT_URL}/user/oauth2/kanidm/callback";
originLanding = "https://git.${homelabDomain}/"; originLanding = "https://git.${homelabDomain}/";
#basicSecretFile = "TODO!SETME"; #basicSecretFile = "TODO!SETME";
scopeMaps."git.users" = [ scopeMaps."git.users" = [
"openid" "openid"
"email" "email"
"profile" "profile"
"groups"
]; ];
# WARNING: PKCE is currently not supported by gitea/forgejo, # WARNING: PKCE is currently not supported by gitea/forgejo,
# see https://github.com/go-gitea/gitea/issues/21376 # see https://github.com/go-gitea/gitea/issues/21376
@ -137,5 +141,5 @@ in
# TODO: Consider automatically creating admin account and password... # TODO: Consider automatically creating admin account and password...
# https://wiki.nixos.org/wiki/Forgejo#Ensure_users # https://wiki.nixos.org/wiki/Forgejo#Ensure_users
# Might be necessary to generate a token for kanidm # Might be necessary to generate a token for kanidm
#sops.secrets.forgejo-admin-password.owner = "forgejo"; 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

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

View file

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

View file

@ -4,29 +4,47 @@ let
svcDomain = "photos.${homelabDomain}"; svcDomain = "photos.${homelabDomain}";
photoStorageDir = "/tank/shares/photos"; photoStorageDir = "/tank/shares/photos";
svcPort = config.services.immich.port; 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 in
{ {
# NOTE: The following repo contains a highly mature immich setup on nixos. # 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 # https://github.com/xinyangli/nixos-config/blob/a8b5bea68caea573801ccfdb8ceacb7a8f2b0190/machines/agate/services/immich.nix
services.caddy.virtualHosts."${svcDomain}".extraConfig = '' services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
reverse_proxy :${svcPort} reverse_proxy :${toString svcPort}
''; '';
# NOTE: Primarily to contain DB_PASSWORD to make it possible to backup and restore the DB. # NOTE: Primarily to contain DB_PASSWORD to make it possible to backup and restore the DB.
sops.secrets.immich_env = { # sops.secrets.immich_env = {
sopsFile = ../../secrets/immich.env; # sopsFile = ../../secrets/immich.env;
format = "dotenv"; # format = "dotenv";
# mode = "0440";
# owner = "immich";
# group = "immich";
# restartUnits = [ "immich.service" ];
# };
sops.secrets."immich/oauth2_client_secret" = { };
sops.templates."immich.json" = {
mode = "0440"; mode = "0440";
owner = "immich"; owner = config.services.immich.user;
group = "immich"; group = config.services.immich.group;
restartUnits = [ "immich.service" ]; content = builtins.toJSON jsonSettings;
};
sops.secrets."immich/oauth2_client_secret" = {
owner = "immich";
group = "kanidm";
mode = "0440";
restartUnits = [ "immich.service" "kanidm.service" ];
}; };
users.users.immich = { users.users.immich = {
@ -45,27 +63,12 @@ in
enable = true; enable = true;
openFirewall = true; openFirewall = true;
port = 2283; # default port = 2283; # default
secretsFile = config.sops.secrets."immich_secrets.env".path; #secretsFile = config.sops.secrets.immich_env.path;
# TODO: Build this directory with permissions for the immich user. # TODO: Build this directory with permissions for the immich user.
mediaLocation = "/tank/shares/photos"; mediaLocation = "/tank/shares/photos";
environment = {
# https://docs.immich.app/install/config-file/ IMMICH_CONFIG_FILE = config.sops.templates."immich.json".path;
settings = {
# TODO: Setup OAuth with Kanidm
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/";
};
}; };
}; };

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

@ -1,7 +1,8 @@
{ config, pkgs, ... }: { config, pkgs, ... }:
let let
svcDomain = "feeds.${config.networking.domain}"; homelabDomain = config.networking.domain;
svcPort = "8080"; svcDomain = "feeds.${homelabDomain}";
svcPort = "8081"; # Prevent a Conflict
in in
{ {
services.caddy.virtualHosts."${svcDomain}".extraConfig = '' services.caddy.virtualHosts."${svcDomain}".extraConfig = ''
@ -22,32 +23,57 @@ in
group = "miniflux"; group = "miniflux";
restartUnits = [ "miniflux.service" ]; restartUnits = [ "miniflux.service" ];
}; };
services.kanidm.provision = { sops.secrets."miniflux/oauth2_client_secret" = {
groups = {}; owner = "miniflux";
systems.oauth2.miniflux = { group = "kanidm";
displayName = "Miniflux Feed Reader"; mode = "0440";
originUrl = "https://${fqdn}/callback"; restartUnits = [ "miniflux.service" "kanidm.service" ];
public = true; # enforces PKCE
preferShortUsername = true;
scopeMaps.pages_users = ["openid" "email" "profile"];
claimMaps."${permissionsMap}".valuesByGroup.pages_admin = ["admin"];
};
}; };
#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 # NOTE: Currently requires some web-interface configuration
services.miniflux = { services.miniflux = {
enable = true; enable = true;
adminCredentialsFile = config.sops.secrets.miniflux_env.path; adminCredentialsFile = config.sops.secrets.miniflux_env.path;
config = { config = {
BASE_URL = "https://${svcDomain}"; BASE_URL = "https://${svcDomain}";
CREATE_ADMIN = 0; #CREATE_ADMIN = 0;
DISABLE_LOCAL_AUTH = 1; #DISABLE_LOCAL_AUTH = 1;
OAUTH2_PROVIDER = "oidc"; OAUTH2_PROVIDER = "oidc";
OAUTH2_OIDC_PROVIDER_NAME = "Kanidm"; OAUTH2_CLIENT_ID = "miniflux";
OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://id.${config.networking.domain}"; OAUTH2_CLIENT_SECRET_FILE = config.sops.secrets."miniflux/oauth2_client_secret".path;
OAUTH2_REDIRECT_URL = "https://${svcDomain}/oauth2/oidc/callback"; OAUTH2_REDIRECT_URL = "https://${svcDomain}/oauth2/oidc/callback";
OAUTH2_USER_CREATION = 1; OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://id.${homelabDomain}/oauth2/openid/miniflux";
#OAUTH2_USER_CREATION = 1;
CLEANUP_FREQUENCY = 48; CLEANUP_FREQUENCY = 48;
LISTEN_ADDR = "localhost:${svcPort}"; 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

@ -1,6 +1,8 @@
{ config, pkgs, ... }: { inputs, config, pkgs, ... }:
let let
svcDomain = "grafana.${config.networking.domain}"; homelabDomain = inputs.nixos-secrets.homelabDomain;
#svcDomain = "grafana.${config.networking.domain}";
svcDomain = "grafana.${homelabDomain}";
svcPort = config.services.grafana.settings.server.http_port; svcPort = config.services.grafana.settings.server.http_port;
in in
{ {

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

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

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

@ -34,13 +34,15 @@ in
''; '';
WorkingDirectory = "/home/palworld"; WorkingDirectory = "/home/palworld";
Restart = "always"; Restart = "always";
RuntimeMaxSec = "1d"; RuntimeMaxSec = "1d"; # NOTE: This thing has memory leaks, restart to save our selves.
User = "palworld"; User = "palworld";
}; };
}; };
# NOTE: Config is stashed at the following directory. # NOTE: Config is stashed at the following directory.
# /home/palworld/.steam/steam/Steamapps/common/PalServer/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini # /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" = { environment.etc."palworld/PalWorldSettings.ini" = {
target = "/home/palworld/.steam/steam/Steamapps/common/PalServer/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini"; target = "/home/palworld/.steam/steam/Steamapps/common/PalServer/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini";
text = palworldSettings; text = palworldSettings;

View file

@ -1,32 +1,39 @@
{ }: { config, ... }:
{ {
services.smartd = { services.smartd = {
enable = true; enable = true;
devices = [ devices = [
{ {
device = "ata-CT500MX500SSD1_2206E607D6AA"; device = "/dev/disk/by-id/ata-CT500MX500SSD1_2206E607D6AA";
} }
{ {
device = "ata-CT500MX500SSD1_2206E607D728"; device = "/dev/disk/by-id/ata-CT500MX500SSD1_2206E607D728";
} }
{ {
device = "ata-ST16000NM001G-2KK103_ZL2B73HT"; device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2B73HT";
} }
{ {
device = "ata-ST16000NM001G-2KK103_ZL2PSELL"; device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2PSELL";
} }
{ {
device = "ata-ST16000NM001G-2KK103_ZL2B4RSM"; device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2B4RSM";
} }
{ {
device = "ata-ST16000NM001G-2KK103_ZL23XYMM"; device = "/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL23XYMM";
} }
{ {
device = "nvme-Samsung_SSD_960_EVO_500GB_S3X4NB0K244331X"; device = "/dev/disk/by-id/nvme-Samsung_SSD_960_EVO_500GB_S3X4NB0K244331X";
} }
{ {
device = "nvme-Samsung_SSD_960_EVO_500GB_S3X4NB0K244303V"; 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";
};
}; };
} }

View file

@ -1,13 +1,15 @@
{ inputs, config, ... }: { inputs, ... }:
let let
secretsPath = builtins.toString inputs.nixos-secrets; secretsPath = builtins.toString inputs.nixos-secrets;
in in
{ {
imports = [ #imports = [ inputs.sops-nix.nixosModules.sops ];
inputs.sops-nix.nixosModules.sops
];
sops = { sops = {
defaultSopsFile = "${secretsPath}/${config.hostname}/secrets.yaml"; #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

@ -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";
};
};
}