feat: reenable immich with native module, prepare nixos-extra-modules update

This commit is contained in:
oddlama 2025-09-13 20:18:54 +02:00
parent ef2f2a9b77
commit 157c303f38
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
25 changed files with 1521 additions and 184 deletions

40
flake/agenix-rekey.nix Normal file
View file

@ -0,0 +1,40 @@
{
inputs,
self,
...
}:
{
imports = [
inputs.agenix-rekey.flakeModule
];
flake = {
# The identities that are used to rekey agenix secrets and to
# decrypt all repository-wide secrets.
secretsConfig = {
masterIdentities = [ "\"$DEVSHELL_DIR\"/secrets/yk1-nix-rage.pub" ];
extraEncryptionPubkeys = [ ../secrets/backup.pub ];
};
};
perSystem =
{ config, ... }:
{
agenix-rekey.nixosConfigurations = self.nodes;
devshells.default = {
commands = [
{
inherit (config.agenix-rekey) package;
help = "Edit, generate and rekey secrets";
}
];
env = [
{
# Always add files to git after agenix rekey and agenix generate.
name = "AGENIX_REKEY_ADD_TO_GIT";
value = "true";
}
];
};
};
}

90
flake/devshell.nix Normal file
View file

@ -0,0 +1,90 @@
{ inputs, ... }:
{
imports = [
inputs.devshell.flakeModule
inputs.pre-commit-hooks.flakeModule
inputs.treefmt-nix.flakeModule
];
perSystem =
{
config,
pkgs,
...
}:
{
pre-commit.settings.hooks.treefmt.enable = true;
treefmt = {
projectRootFile = "flake.nix";
programs = {
deadnix.enable = true;
statix.enable = true;
nixfmt.enable = true;
rustfmt.enable = true;
};
};
devshells.default = {
packages = [
pkgs.nix # Always use the nix version from this flake's nixpkgs version, so that nix-plugins (below) doesn't fail because of different nix versions.
];
commands = [
{
package = config.treefmt.build.wrapper;
help = "Format all files";
}
{
package = pkgs.deploy;
help = "Build and deploy this nix config to nodes";
}
{
package = pkgs.nix-tree;
help = "Interactively browse dependency graphs of Nix derivations";
}
{
package = pkgs.nvd;
help = "Diff two nix toplevels and show which packages were upgraded";
}
{
package = pkgs.nix-diff;
help = "Explain why two Nix derivations differ";
}
{
package = pkgs.nix-output-monitor;
help = "Nix Output Monitor (a drop-in alternative for `nix` which shows a build graph)";
}
{
package = pkgs.writeShellApplication {
name = "build";
text = ''
set -euo pipefail
[[ "$#" -ge 1 ]] \
|| { echo "usage: build <HOST>..." >&2; exit 1; }
HOSTS=()
for h in "$@"; do
HOSTS+=(".#nixosConfigurations.$h.config.system.build.toplevel")
done
nom build --no-link --print-out-paths --show-trace "''${HOSTS[@]}"
'';
};
help = "Build a host configuration";
}
];
devshell.startup.pre-commit.text = config.pre-commit.installationScript;
env = [
{
# Additionally configure nix-plugins with our extra builtins file.
# We need this for our repo secrets.
name = "NIX_CONFIG";
value = ''
plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins
extra-builtins-file = ${./..}/flake/extra-builtins.nix
'';
}
];
};
};
}

45
flake/extra-builtins.nix Normal file
View file

@ -0,0 +1,45 @@
# This file is intended to be used together with pkgs.nix-plugins,
# to provide rage decryption as an additional safe builtin.
#
# Make sure that nix-plugins is installed by adding the following
# statement to your configuration.nix:
#
# ```nix
# {
# nix.extraOptions = ''
# plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins
# # Please adjust path accordingly, or leave this out and alternativaly
# # pass `--option extra-builtins-file ./extra-builtins.nix` to each invocation
# extra-builtins-file = ${./extra-builtins.nix}
# '';
# }
# ```
{ exec, ... }:
let
assertMsg = pred: msg: pred || builtins.throw msg;
hasSuffix =
suffix: content:
let
lenContent = builtins.stringLength content;
lenSuffix = builtins.stringLength suffix;
in
lenContent >= lenSuffix && builtins.substring (lenContent - lenSuffix) lenContent content == suffix;
in
{
# Instead of calling rage directly here, we call a wrapper script that will cache the output
# in a predictable path in /tmp, which allows us to only require the password for each encrypted
# file once.
rageImportEncrypted =
identities: nixFile:
assert assertMsg (builtins.isPath nixFile)
"The file to decrypt must be given as a path to prevent impurity.";
assert assertMsg (hasSuffix ".nix.age" nixFile)
"The content of the decrypted file must be a nix expression and should therefore end in .nix.age";
exec (
[
./rage-decrypt-and-cache.sh
nixFile
]
++ identities
);
}

22
flake/globals.nix Normal file
View file

@ -0,0 +1,22 @@
{
globals = {
optModules = [
../modules/globals.nix
];
defModules = [
../globals.nix
];
attrkeys = [
"domains"
"hetzner"
"kanidm"
"macs"
"mail"
"monitoring"
"myuser"
"net"
"root"
"services"
];
};
}

95
flake/hosts.nix Normal file
View file

@ -0,0 +1,95 @@
{ inputs, ... }:
{
flake =
{
config,
lib,
...
}:
let
inherit (lib)
concatMapAttrs
filterAttrs
flip
genAttrs
mapAttrs
mapAttrs'
nameValuePair
;
# Creates a new nixosSystem with the correct specialArgs, pkgs and name definition
mkHost =
{ minimal }:
name:
let
pkgs = config.pkgs.x86_64-linux; # FIXME: NOOOOOOOOOOOOOOOOOOOOOOO
in
inputs.nixpkgs.lib.nixosSystem {
specialArgs = {
# Use the correct instance lib that has our overlays
inherit (pkgs) lib;
inherit (config) nodes globals;
inherit inputs minimal;
};
modules = [
{
nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [
"qtwebengine-5.15.19" # teamspeak3, whatever I don't visit any untrusted servers
];
nixpkgs.overlays = (import ../pkgs/default.nix inputs) ++ [
inputs.idmail.overlays.default
# inputs.nixos-cosmic.overlays.default
inputs.nix-topology.overlays.default
inputs.nixos-extra-modules.overlays.default
inputs.nixvim.overlays.default
];
node.name = name;
node.secretsDir = ../hosts/${name}/secrets;
}
../hosts/${name}
];
};
# Get all folders in hosts/
hosts = builtins.attrNames (filterAttrs (_: type: type == "directory") (builtins.readDir ../hosts));
in
{
nixosConfigurations = genAttrs hosts (mkHost {
minimal = false;
});
nixosConfigurationsMinimal = genAttrs hosts (mkHost {
minimal = true;
});
# True NixOS nodes can define additional guest nodes that are built
# together with it. We collect all defined guests from each node here
# to allow accessing any node via the unified attribute `nodes`.
guestConfigs = flip concatMapAttrs config.nixosConfigurations (
_: node:
flip mapAttrs' (node.config.guests or { }) (
guestName: guestDef:
nameValuePair guestDef.nodeName (
if guestDef.backend == "microvm" then
node.config.microvm.vms.${guestName}.config
else
node.config.containers.${guestName}.nixosConfiguration
)
)
);
# All nixosSystem instanciations are collected here, so that we can refer
# to any system via nodes.<name>
nodes = config.nixosConfigurations // config.guestConfigs;
# Add a shorthand to easily target toplevel derivations
"@" = mapAttrs (_: v: v.config.system.build.toplevel) config.nodes;
# Pre-evaluate the wireguard network information to avoid recalculating it
# for every host and every location it is used.
wireguardEvalCache = config.pkgs.x86_64-linux.lib.wireguard.createEvalCache inputs [
"proxy-sentinel"
"proxy-home"
];
};
}

View file

@ -0,0 +1,38 @@
{ pkgs, ... }:
{
system.stateVersion = "24.11";
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
console.keyMap = "de-latin1-nodeadkeys";
boot.loader.systemd-boot.enable = true;
users.users.root = {
password = "nixos";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA5Uq+CDy5Pmt3If5M6d8K/Q7HArU6sZ7sgoj3T521Wm"
];
};
environment = {
variables.EDITOR = "nvim";
systemPackages = with pkgs; [
neovim
git
tmux
parted
ripgrep
fzf
wget
curl
];
etc.issue.text = ''
\d \t
This is \e{cyan}\n\e{reset} [\e{lightblue}\l\e{reset}] (\s \m \r)
\e{halfbright}\4\e{reset} \e{halfbright}\6\e{reset}
'';
};
}

27
flake/iso.nix Normal file
View file

@ -0,0 +1,27 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
system,
...
}:
{
# For each major system, we provide a customized installer image that
# has ssh and some other convenience stuff preconfigured.
# Not strictly necessary for new setups.
packages.live-iso = inputs.nixos-generators.nixosGenerate {
inherit pkgs;
modules = [
./installer-configuration.nix
../config/ssh.nix
];
format =
{
x86_64-linux = "install-iso";
aarch64-linux = "sd-aarch64-installer";
}
.${system};
};
};
}

39
flake/pkgs.nix Normal file
View file

@ -0,0 +1,39 @@
{ inputs, ... }:
{
imports = [
(
{
lib,
flake-parts-lib,
...
}:
flake-parts-lib.mkTransposedPerSystemModule {
name = "pkgs";
file = ./pkgs.nix;
option = lib.mkOption {
type = lib.types.unspecified;
};
}
)
];
perSystem =
{
pkgs,
system,
...
}:
{
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = (import ../pkgs/default.nix inputs) ++ [
inputs.nix-topology.overlays.default
# inputs.nixos-cosmic.overlays.default
inputs.nixos-extra-modules.overlays.default
];
};
inherit pkgs;
};
}

44
flake/rage-decrypt-and-cache.sh Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
print_out_path=false
if [[ "$1" == "--print-out-path" ]]; then
print_out_path=true
shift
fi
file="$1"
shift
identities=("$@")
# Strip .age suffix, and store path prefix or ./ if applicable
basename="${file%".age"}"
[[ "$file" == "/nix/store/"* ]] && basename="${basename#*"-"}"
[[ "$file" == "./"* ]] && basename="${basename#"./"}"
# Calculate a unique content-based identifier (relocations of
# the source file in the nix store should not affect caching)
new_name="$(sha512sum "$file")"
new_name="${new_name:0:32}-${basename//"/"/"%"}"
# Derive the path where the decrypted file will be stored
out="/var/tmp/nix-import-encrypted/$UID/$new_name"
umask 077
mkdir -p "$(dirname "$out")"
# Decrypt only if necessary
if [[ ! -e "$out" ]]; then
args=()
for i in "${identities[@]}"; do
args+=("--identity" "$i")
done
rage --decrypt "${args[@]}" --output "$out" "$file"
fi
# Print out path or decrypted content
if [[ "$print_out_path" == true ]]; then
echo "$out"
else
cat "$out"
fi

14
flake/storage-box.nix Normal file
View file

@ -0,0 +1,14 @@
{ inputs, ... }:
{
perSystem =
{ pkgs, ... }:
{
apps.setupHetznerStorageBoxes =
import (inputs.nixos-extra-modules + "/apps/setup-hetzner-storage-boxes.nix")
{
inherit pkgs;
nixosConfigurations = inputs.self.nodes;
decryptIdentity = builtins.head inputs.self.secretsConfig.masterIdentities;
};
};
}