feat: new deploy system, allows for in-repo pii

This commit is contained in:
Leon Schwarzäugl 2025-06-11 02:25:34 +02:00
parent 7e11641fe7
commit a11c7854d1
Signed by: swarsel
GPG key ID: 26A54C31F2A4FD84
19 changed files with 1251 additions and 412 deletions

View file

@ -340,66 +340,74 @@ In this section I am creating some attributes that define general concepts of my
They are defined in [[#h:5e3e21e0-57af-4dad-b32f-6400af9b7aab][Overlays (additions, overrides, nixpkgs-stable)]]. The way this is handled was simplified in =647a2ae feat: simplify overlay structure=; however, the old structure might be easier to understand as a reference.
#+begin_src nix :tangle no :noweb-ref flakeoutputgeneral
inherit lib;
inherit lib;
# nixosModules = import ./modules/nixos { inherit lib; };
# homeModules = import ./modules/home { inherit lib; };
packages = lib.swarselsystems.forEachSystem (pkgs: import ./pkgs { inherit lib pkgs; });
formatter = lib.swarselsystems.forEachSystem (pkgs: pkgs.nixpkgs-fmt);
overlays = import ./overlays { inherit self lib inputs; };
# nixosModules = import ./modules/nixos { inherit lib; };
# homeModules = import ./modules/home { inherit lib; };
packages = lib.swarselsystems.forEachSystem (pkgs: import ./pkgs { inherit lib pkgs; });
formatter = lib.swarselsystems.forEachSystem (pkgs: pkgs.nixpkgs-fmt);
overlays = import ./overlays { inherit self lib inputs; };
apps = lib.swarselsystems.forAllSystems (system:
let
appNames = [
"swarsel-bootstrap"
"swarsel-install"
"swarsel-rebuild"
"swarsel-postinstall"
];
appSet = lib.swarselsystems.mkApps system appNames self;
in
appSet // {
default = appSet.swarsel-bootstrap;
}
);
devShells = lib.swarselsystems.forAllSystems (system:
let
pkgs = lib.swarselsystems.pkgsFor.${system};
checks = self.checks.${system};
in
{
default = pkgs.mkShell {
NIX_CONFIG = "experimental-features = nix-command flakes";
inherit (checks.pre-commit-check) shellHook;
buildInputs = checks.pre-commit-check.enabledPackages;
nativeBuildInputs = [
pkgs.nix
pkgs.home-manager
pkgs.git
pkgs.just
pkgs.age
pkgs.ssh-to-age
pkgs.sops
pkgs.statix
pkgs.deadnix
pkgs.nixpkgs-fmt
apps = lib.swarselsystems.forAllSystems (system:
let
appNames = [
"swarsel-bootstrap"
"swarsel-install"
"swarsel-rebuild"
"swarsel-postinstall"
];
};
}
);
appSet = lib.swarselsystems.mkApps system appNames self;
in
templates = import ./templates { inherit lib; };
appSet // {
default = appSet.swarsel-bootstrap;
}
);
checks = lib.swarselsystems.forAllSystems (system:
let
pkgs = lib.swarselsystems.pkgsFor.${system};
in
import ./checks { inherit self inputs system pkgs; }
);
devShells = lib.swarselsystems.forAllSystems (system:
let
pkgs = lib.swarselsystems.pkgsFor.${system};
checks = self.checks.${system};
in
{
default = pkgs.mkShell {
NIX_CONFIG = ''
plugin-files = ${pkgs.nix-plugins.overrideAttrs (o: {
buildInputs = [pkgs.nixVersions.latest pkgs.boost];
patches = (o.patches or []) ++ [ "${self}/nix/nix-plugins.patch" ];
})}/lib/nix/plugins
extra-builtins-file = ${self + /nix/extra-builtins.nix}
'';
inherit (checks.pre-commit-check) shellHook;
diskoConfigurations.default = import .templates/hosts/nixos/disk-config.nix;
buildInputs = checks.pre-commit-check.enabledPackages;
nativeBuildInputs = [
# (builtins.trace "alarm: we pinned nix_2_24 because of https://github.com/shlevy/nix-plugins/issues/20" pkgs.nixVersions.nix_2_24) # Always use the nix version from this flake's nixpkgs version, so that nix-plugins (below) doesn't fail because of different nix versions.
pkgs.nix
pkgs.home-manager
pkgs.git
pkgs.just
pkgs.age
pkgs.ssh-to-age
pkgs.sops
pkgs.statix
pkgs.deadnix
pkgs.nixpkgs-fmt
];
};
}
);
templates = import ./templates { inherit lib; };
checks = lib.swarselsystems.forAllSystems (system:
let
pkgs = lib.swarselsystems.pkgsFor.${system};
in
import ./checks { inherit self inputs system pkgs; }
);
diskoConfigurations.default = import .templates/hosts/nixos/disk-config.nix;
#+end_src
** Pre-commit-hooks (Checks)
@ -849,7 +857,7 @@ My work machine. Built for more security, this is the gold standard of my config
sharedOptions;
home-manager.users."${primaryUser}" = {
home.stateVersion = lib.mkForce "23.05";
# home.stateVersion = lib.mkForce "23.05";
swarselsystems = lib.recursiveUpdate
{
isLaptop = true;
@ -3685,6 +3693,136 @@ AppImage version of mgba in which the lua scripting works.
#+end_src
**** swarsel-deploy
#+begin_src nix :tangle pkgs/swarsel-deploy/default.nix
# heavily inspired from https://github.com/oddlama/nix-config/blob/d42cbde676001a7ad8a3cace156e050933a4dcc3/pkgs/deploy.nix
{ name, bc, nix-output-monitor, writeShellApplication, ... }:
writeShellApplication {
runtimeInputs = [ bc nix-output-monitor ];
inherit name;
text = ''
set -euo pipefail
shopt -s lastpipe # allow cmd | readarray
function die() {
echo "error: $*" >&2
exit 1
}
function show_help() {
echo 'Usage: deploy [OPTIONS] <host,...> [ACTION]'
echo "Builds, pushes and activates nixosConfigurations on target systems."
echo ""
echo 'ACTION:'
echo ' switch [default] Switch immediately to the new configuration and make it the boot default'
echo ' boot Make the configuration the new boot default'
echo " test Activate the configuration but don't make it the boot default"
echo " dry-activate Don't activate, just show what would be done"
echo ""
echo 'OPTIONS: [passed to nix build]'
}
function time_start() {
T_START=$(date +%s.%N)
}
function time_next() {
T_END=$(date +%s.%N)
T_LAST=$(${bc}/bin/bc <<< "scale=1; ($T_END - $T_START)/1")
T_START="$T_END"
}
cd ~/.dotfiles
USER_FLAKE_DIR=$(git rev-parse --show-toplevel 2> /dev/null || pwd) ||
die "Could not determine current working directory. Something went very wrong."
[[ -e "$USER_FLAKE_DIR/flake.nix" ]] ||
die "Could not determine location of your project's flake.nix. Please run this at or below your main directory containing the flake.nix."
cd "$USER_FLAKE_DIR"
[[ $# -gt 0 ]] || {
show_help
exit 1
}
OPTIONS=()
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
"help" | "--help" | "-help" | "-h")
show_help
exit 1
;;
-*) OPTIONS+=("$1") ;;
,*) POSITIONAL_ARGS+=("$1") ;;
esac
shift
done
[[ ''${#POSITIONAL_ARGS[@]} -ge 1 ]] ||
die "Missing argument: <hosts...>"
[[ ''${#POSITIONAL_ARGS[@]} -le 2 ]] ||
die "Too many arguments given."
tr , '\n' <<< "''${POSITIONAL_ARGS[0]}" | sort -u | readarray -t HOSTS
ACTION="''${POSITIONAL_ARGS[1]-switch}"
# Expand flake paths for hosts definitions
declare -A TOPLEVEL_FLAKE_PATHS
for host in "''${HOSTS[@]}"; do
TOPLEVEL_FLAKE_PATHS["$host"]=".#nixosConfigurations.$host.config.system.build.toplevel"
done
time_start
# Get outputs of all derivations (should be cached)
declare -A TOPLEVEL_STORE_PATHS
for host in "''${HOSTS[@]}"; do
toplevel="''${TOPLEVEL_FLAKE_PATHS["$host"]}"
echo " Building 📦 $host"
nix build --no-link "''${OPTIONS[@]}" --show-trace --log-format internal-json -v "$toplevel" |& ${nix-output-monitor}/bin/nom --json ||
die "Failed to get derivation path for $host from ''${TOPLEVEL_FLAKE_PATHS["$host"]}"
TOPLEVEL_STORE_PATHS["$host"]=$(nix build --no-link --print-out-paths "''${OPTIONS[@]}" "$toplevel")
time_next
echo " Built ✅ $host ''${TOPLEVEL_STORE_PATHS["$host"]} in ''${T_LAST}s"
done
current_host=$(hostname)
for host in "''${HOSTS[@]}"; do
store_path="''${TOPLEVEL_STORE_PATHS["$host"]}"
if [ "$host" = "$current_host" ]; then
echo -e "\033[1;36m Running locally for $host... \033[m"
ssh_prefix="sudo"
else
echo -e "\033[1;36m Copying \033[m➡ \033[34m$host\033[m"
nix copy --to "ssh://$host" "$store_path"
time_next
echo -e "\033[1;32m Copied \033[m✅ \033[34m$host\033[m \033[90min ''${T_LAST}s\033[m"
ssh_prefix="ssh $host --"
fi
echo -e "\033[1;36m Applying \033[m⚙ \033[34m$host\033[m"
prev_system=$($ssh_prefix readlink -e /nix/var/nix/profiles/system)
$ssh_prefix /run/current-system/sw/bin/nix-env --profile /nix/var/nix/profiles/system --set "$store_path" ||
die "Failed to set system profile"
$ssh_prefix "$store_path"/bin/switch-to-configuration "$ACTION" ||
echo "Error while activating new system" >&2
if [[ -n $prev_system ]]; then
$ssh_prefix nvd --color always diff "$prev_system" "$store_path" || true
fi
time_next
echo -e "\033[1;32m Applied \033[m✅ \033[34m$host\033[m \033[90min ''${T_LAST}s\033[m"
done
cd -
'';
}
#+end_src
**** sshrm
This programs simply runs ssh-keygen on the last host that I tried to ssh into. I need this frequently when working with cloud-init usually.
@ -3886,6 +4024,10 @@ Modules that need to be loaded on the NixOS level. Note that these will not be a
autologin = lib.mkDefault true;
nswitch-rcm = lib.mkDefault true;
};
server = {
ssh = lib.mkDefault true;
};
};
};
@ -4490,8 +4632,6 @@ TODO
{
home-manager.users."${linuxUser}".imports = [
# put home-manager imports here that are for all normal hosts
inputs.sops-nix.homeManagerModules.sops
inputs.nix-index-database.hmModules.nix-index
"${self}/modules/home/common"
"${self}/modules/home/server"
"${self}/modules/home/optional"
@ -4597,6 +4737,83 @@ TODO
}
#+end_src
*** Auxiliary files
**** extra-builtins
#+begin_src nix :tangle nix/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 sops 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.
sopsImportEncrypted =
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 [
./sops-decrypt-and-cache.sh
nixFile
];
}
#+end_src
**** sops-decrypt-and-cache
#+begin_src shell :tangle nix/sops-decrypt-and-cache.sh
#!/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
basename="$file"
# store path prefix or ./ if applicable
[[ $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
agekey=$(sudo ssh-to-age -private-key -i /etc/ssh/sops || sudo ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key)
SOPS_AGE_KEY="$agekey" sops decrypt --output "$out" "$file"
fi
# Print out path or decrypted content
if [[ $print_out_path == true ]]; then
echo "$out"
else
cat "$out"
fi
#+end_src
** NixOS
:PROPERTIES:
:CUSTOM_ID: h:6da812f5-358c-49cb-aff2-0a94f20d70b3
@ -4858,6 +5075,14 @@ We enable the use of =home-manager= as a NixoS module. A nice trick here is the
home-manager = lib.mkIf config.swarselsystems.withHomeManager {
useGlobalPkgs = true;
useUserPackages = true;
verbose = true;
sharedModules = [
inputs.nix-index-database.hmModules.nix-index
inputs.sops-nix.homeManagerModules.sops
{
home.stateVersion = lib.mkDefault config.system.stateVersion;
}
];
extraSpecialArgs = { inherit (inputs) self; };
};
};
@ -6450,7 +6675,7 @@ This dynamically uses systemd boot or Lanzaboote depending on `config.swarselsys
lanzaboote = lib.mkIf (!config.swarselsystems.initialSetup && config.swarselsystems.isSecureBoot) {
enable = true;
pkiBundle = "/var/lib/sbctl";
configurationLimit = 3;
configurationLimit = 6;
};
};
};
@ -6555,6 +6780,7 @@ Here we just define some aliases for rebuilding the system, and we allow some in
environment.systemPackages = with pkgs; [
gnupg
nix-index
nvd
ssh-to-age
git
emacs
@ -6695,6 +6921,8 @@ Here we just define some aliases for rebuilding the system, and we allow some in
:CUSTOM_ID: h:f3db197d-1d03-4bf8-b59f-f9891b358f0b
:END:
Here I am forcing =startWhenNeeded= to false so that the value will not be set to true in containers = this would be a problem because it would delay ssh host key generation.
#+begin_src nix :tangle modules/nixos/server/ssh.nix
{ self, lib, config, ... }:
{
@ -6702,6 +6930,18 @@ Here we just define some aliases for rebuilding the system, and we allow some in
config = lib.mkIf config.swarselsystems.modules.server.ssh {
services.openssh = {
enable = true;
startWhenNeeded = lib.mkForce false;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "yes";
};
hostKeys = [
{
path = "/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
};
users.users."${config.swarselsystems.mainUser}".openssh.authorizedKeys.keyFiles = [
(self + /secrets/keys/ssh/yubikey.pub)
@ -10166,6 +10406,7 @@ This is just a separate container for derivations defined in [[#h:64a5cc16-6b16-
fhs
swarsel-bootstrap
swarsel-displaypower
swarsel-deploy
swarselzellij
sshrm
@ -10266,7 +10507,7 @@ It is very convenient to have SSH aliases in place for machines that I use. This
};
"winters" = {
hostname = "192.168.1.2";
user = "swarsel";
user = "root";
};
"minecraft" = {
hostname = "130.61.119.129";
@ -10943,8 +11184,10 @@ Currently I only use it as before with =initExtra= though.
{
hg = "history | grep";
hmswitch = "home-manager --flake ${flakePath}#$(whoami)@$(hostname) switch |& nom";
nswitch = "sudo nixos-rebuild --flake ${flakePath}#$(hostname) --show-trace --log-format internal-json -v switch |& nom --json";
nboot = "sudo nixos-rebuild --flake ${flakePath}#$(hostname) --show-trace --log-format internal-json -v boot |& nom --json";
# nswitch = "sudo nixos-rebuild --flake ${flakePath}#$(hostname) --show-trace --log-format internal-json -v switch |& nom --json";
nswitch = "swarsel-deploy $(hostname) switch";
# nboot = "sudo nixos-rebuild --flake ${flakePath}#$(hostname) --show-trace --log-format internal-json -v boot |& nom --json";
nboot = "swarsel-deploy $(hostname) boot";
magit = "emacsclient -nc -e \"(magit-status)\"";
config = "git --git-dir=$HOME/.cfg/ --work-tree=$HOME";
g = "git";