Compare commits

...

1 commit

10 changed files with 380 additions and 271 deletions

View file

@ -1,15 +1,49 @@
{ flake-inputs }: { self, ... }:
let
inherit (flake-inputs.nixpkgs) lib;
pkgs = flake-inputs.nixpkgs.legacyPackages.x86_64-linux;
checkLib = pkgs.callPackage ./lib.nix { };
in
{ {
x86_64-linux = lib.mergeAttrsList [ perSystem =
flake-inputs.self.nixosConfigurations.hetzner-1.config.serviceTests
{ {
nix = checkLib.mkLint { inputs',
lib,
pkgs,
...
}:
let
mkLint =
{
name,
fileset,
checkInputs ? [ ],
script,
}:
pkgs.stdenvNoCC.mkDerivation {
inherit name;
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.difference fileset (
lib.fileset.fileFilter (
file: file.type != "regular" || file.name == "hardware-configuration.nix"
) ../.
);
};
checkInputs = [ pkgs.nushell ] ++ checkInputs;
checkPhase = ''
nu -c '${script}' | tee $out
'';
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
dontFixup = true;
doCheck = true;
};
in
{
checks = {
nix = mkLint {
name = "nix-lints"; name = "nix-lints";
fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.; fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.;
@ -26,22 +60,23 @@ in
}); });
}; };
script = '' script = /* bash */ ''
statix check **/*.nix statix check **/*.nix
deadnix --fail **/*.nix deadnix --fail **/*.nix
nixfmt --check --strict **/*.nix nixfmt --check --strict **/*.nix
''; '';
}; };
lockfile = checkLib.mkLint { lockfile = mkLint {
name = "nix-lockfile"; name = "nix-lockfile";
fileset = ../flake.lock; fileset = ../flake.lock;
checkInputs = lib.attrValues { inherit (flake-inputs.flint.packages.x86_64-linux) flint; }; checkInputs = lib.attrValues { inherit (inputs'.flint.packages) flint; };
script = '' script = /* bash */ ''
flint --fail-if-multiple-versions flint --fail-if-multiple-versions
''; '';
}; };
} }
]; // self.nixosConfigurations.hetzner-1.config.serviceTests;
};
} }

View file

@ -1,35 +0,0 @@
{ pkgs, lib, ... }:
{
mkLint =
{
name,
fileset,
checkInputs ? [ ],
script,
}:
pkgs.stdenvNoCC.mkDerivation {
inherit name;
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.difference fileset (
lib.fileset.fileFilter (
file: file.type != "regular" || file.name == "hardware-configuration.nix"
) ../.
);
};
checkInputs = [ pkgs.nushell ] ++ checkInputs;
checkPhase = ''
nu -c '${script}' | tee $out
'';
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
dontFixup = true;
doCheck = true;
};
}

View file

@ -2,6 +2,7 @@
imports = [ imports = [
./hardware-configuration.nix ./hardware-configuration.nix
./disko.nix ./disko.nix
./vm.nix
]; ];
# Intel's special encrypted memory<->CPU feature. Hetzner's BIOS # Intel's special encrypted memory<->CPU feature. Hetzner's BIOS

View file

@ -0,0 +1,70 @@
{ lib, ... }:
{
virtualisation.vmVariant = {
users.users.tlater.password = "insecure";
# Disable graphical tty so -curses works
boot.kernelParams = [ "nomodeset" ];
networking.hostName = lib.mkForce "testvm";
services = {
# Sets the base domain for nginx to a local domain so that we can
# easily test locally with the VM.
nginx.domain = lib.mkForce "dev.local";
# Don't run this
batteryManager.enable = lib.mkForce false;
btrfs.autoScrub.enable = lib.mkForce false;
openssh.hostKeys = lib.mkForce [
{
type = "rsa";
bits = 4096;
path = "/etc/staging.key";
}
];
};
# Use the staging secrets
sops.defaultSopsFile = lib.mkOverride 99 ../../../keys/staging.yaml;
systemd.network.networks."10-eth0" = {
matchConfig.Name = "eth0";
gateway = [ "192.168.9.1" ];
networkConfig = {
Address = "192.168.9.2/24";
};
};
# Both so we have a predictable key for the staging env, as well as
# to have a static key for decrypting the sops secrets for the
# staging env.
environment.etc."staging.key" = {
mode = "0400";
source = ../../../keys/hosts/staging.key;
};
# Pretend the acme renew succeeds.
#
# TODO(tlater): Set up pebble to retrieve certs "properly"
# instead
systemd.services."acme-order-renew-tlater.net".script = ''
touch out/acme-success
'';
virtualisation = {
memorySize = 3941;
cores = 2;
graphics = false;
diskSize = 1024 * 20;
qemu = {
networkingOptions = lib.mkForce [
"-device virtio-net,netdev=n1"
"-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)"
];
};
};
};
}

View file

@ -1,69 +0,0 @@
{ lib, ... }:
{
users.users.tlater.password = "insecure";
# Disable graphical tty so -curses works
boot.kernelParams = [ "nomodeset" ];
networking.hostName = "testvm";
services = {
# Sets the base domain for nginx to a local domain so that we can
# easily test locally with the VM.
nginx.domain = "dev.local";
# Don't run this
batteryManager.enable = lib.mkForce false;
openssh.hostKeys = lib.mkForce [
{
type = "rsa";
bits = 4096;
path = "/etc/staging.key";
}
];
};
# Use the staging secrets
sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml;
systemd.network.networks."10-eth0" = {
matchConfig.Name = "eth0";
gateway = [ "192.168.9.1" ];
networkConfig = {
Address = "192.168.9.2/24";
};
};
# Both so we have a predictable key for the staging env, as well as
# to have a static key for decrypting the sops secrets for the
# staging env.
environment.etc."staging.key" = {
mode = "0400";
source = ../../keys/hosts/staging.key;
};
# Pretend the acme renew succeeds.
#
# TODO(tlater): Set up pebble to retrieve certs "properly"
# instead
systemd.services."acme-order-renew-tlater.net".script = ''
touch out/acme-success
'';
virtualisation.vmVariant = {
virtualisation = {
memorySize = 3941;
cores = 2;
graphics = false;
diskSize = 1024 * 20;
};
virtualisation.qemu = {
networkingOptions = lib.mkForce [
"-device virtio-net,netdev=n1"
"-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)"
];
};
};
}

43
dev-utils.nix Normal file
View file

@ -0,0 +1,43 @@
{ self, ... }:
{
# Systems on which to make dev utilities runnable; anything
# NixOS-related encodes its own system.
systems = [ "x86_64-linux" ];
perSystem =
{
inputs',
self',
pkgs,
lib,
...
}:
{
apps = {
default = self'.apps.runVm;
runVm = {
type = "app";
program = lib.getExe self.nixosConfigurations.hetzner-1.config.system.build.vm;
meta.description = "Run the test VM";
};
};
devShells = {
default = pkgs.mkShell {
sopsPGPKeyDirs = [
"./keys/hosts/"
"./keys/users/"
];
packages = lib.attrValues {
inherit (inputs'.sops-nix.packages) sops-import-keys-hook sops-init-gpg-key;
inherit (pkgs) deploy-rs;
};
};
minecraft = pkgs.mkShell { packages = lib.attrValues { inherit (pkgs) packwiz; }; };
webserver = self'.packages.webserver.devShell;
};
};
}

32
flake.lock generated
View file

@ -46,7 +46,9 @@
"deploy-rs", "deploy-rs",
"flake-compat" "flake-compat"
], ],
"flake-parts": "flake-parts", "flake-parts": [
"flake-parts"
],
"nix-test-runner": "nix-test-runner", "nix-test-runner": "nix-test-runner",
"nixpkgs": [ "nixpkgs": [
"sonnenshift", "sonnenshift",
@ -154,18 +156,14 @@
}, },
"flake-parts": { "flake-parts": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": "nixpkgs-lib"
"sonnenshift",
"crate2nix",
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1712014858, "lastModified": 1769996383,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -266,6 +264,21 @@
"url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz" "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"
} }
}, },
"nixpkgs-lib": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "72716169fe93074c333e8d0173151350670b824c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"pre-commit-hooks": { "pre-commit-hooks": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
@ -309,6 +322,7 @@
"inputs": { "inputs": {
"deploy-rs": "deploy-rs", "deploy-rs": "deploy-rs",
"disko": "disko", "disko": "disko",
"flake-parts": "flake-parts",
"flint": "flint", "flint": "flint",
"foundryvtt": "foundryvtt", "foundryvtt": "foundryvtt",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",

121
flake.nix
View file

@ -1,8 +1,7 @@
{ {
description = "tlater.net host configuration";
inputs = { inputs = {
nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"; nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz";
flake-parts.url = "github:hercules-ci/flake-parts";
## Nix/OS utilities ## Nix/OS utilities
@ -42,78 +41,39 @@
crate2nix.inputs = { crate2nix.inputs = {
flake-compat.follows = "deploy-rs/flake-compat"; flake-compat.follows = "deploy-rs/flake-compat";
devshell.inputs.flake-utils.follows = "deploy-rs/utils"; devshell.inputs.flake-utils.follows = "deploy-rs/utils";
flake-parts.follows = "flake-parts";
}; };
}; };
}; };
}; };
outputs = outputs =
{ flake-parts, ... }@inputs:
flake-parts.lib.mkFlake { inherit inputs; } (
{ self, ... }@args:
{ {
self, imports = [
nixpkgs, (flake-parts.lib.importApply ./flakeModules/deploy-rs.nix args)
sops-nix, ./checks
... ./dev-utils.nix
}@inputs: ./pkgs
let
system = "x86_64-linux";
vm = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs.flake-inputs = inputs;
modules = [
./configuration
./configuration/hardware-specific/vm.nix
]; ];
};
# deploy-rs unfortunately uses an `import nixpkgs`, and its flake.nixosConfigurations.hetzner-1 = inputs.nixpkgs.lib.nixosSystem {
# library functions depend on an instantiated nixpkgs, so we
# can't get around multi-nixpkgs-eval.
inherit
(import nixpkgs {
inherit system;
overlays = [
inputs.deploy-rs.overlays.default
(_: prev: {
deploy-rs = {
inherit (nixpkgs.legacyPackages.${system}) deploy-rs;
inherit (prev.deploy-rs) lib;
};
})
];
})
deploy-rs
;
in
{
##################
# Configurations #
##################
nixosConfigurations = {
# The actual system definition
hetzner-1 = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs.flake-inputs = inputs; specialArgs.flake-inputs = inputs;
modules = [ modules = [
./configuration ./configuration
./configuration/hardware-specific/hetzner ./configuration/hardware-specific/hetzner
]; ];
}; };
};
############################ deploy.nodes.hetzner-1 = {
# Deployment configuration #
############################
deploy.nodes = {
hetzner-1 = {
hostname = "116.202.158.55"; hostname = "116.202.158.55";
profiles.system = { profiles.system = {
user = "root"; user = "root";
path = deploy-rs.lib.activate.nixos self.nixosConfigurations.hetzner-1; activation = "nixos";
}; closure = self.nixosConfigurations.hetzner-1;
sshUser = "tlater"; sshUser = "tlater";
sshOpts = [ sshOpts = [
@ -124,57 +84,6 @@
]; ];
}; };
}; };
#########
# Tests #
#########
checks = import ./checks { flake-inputs = inputs; };
###########################
# Garbage collection root #
###########################
packages.${system} = {
default = vm.config.system.build.vm;
} }
// import ./pkgs { pkgs = nixpkgs.legacyPackages.${system}; }; );
###################
# Utility scripts #
###################
apps.${system} = {
default = self.apps.${system}.run-vm;
run-vm = {
type = "app";
program =
(nixpkgs.legacyPackages.${system}.writeShellScript "" ''
${vm.config.system.build.vm.outPath}/bin/run-testvm-vm
'').outPath;
};
};
###########################
# Development environment #
###########################
devShells.${system} = {
default = nixpkgs.legacyPackages.${system}.mkShell {
sopsPGPKeyDirs = [
"./keys/hosts/"
"./keys/users/"
];
packages = nixpkgs.lib.attrValues {
inherit (sops-nix.packages.${system}) sops-import-keys-hook sops-init-gpg-key;
inherit (deploy-rs) deploy-rs;
};
};
minecraft = nixpkgs.legacyPackages.${system}.mkShell {
packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; };
};
webserver = self.packages.${system}.webserver.devShell;
};
};
} }

136
flakeModules/deploy-rs.nix Normal file
View file

@ -0,0 +1,136 @@
{ lib, ... }@exportingFlake:
let
inherit (lib) mkOption types;
deploy-rs-for-system =
system:
(import exportingFlake.inputs.nixpkgs {
inherit system;
overlays = [
exportingFlake.inputs.deploy-rs.overlays.default
(_final: prev: {
deploy-rs = {
inherit (exportingFlake.inputs.nixpkgs.legacyPackages.${system}) deploy-rs;
inherit (prev.deploy-rs) lib;
};
})
];
}).deploy-rs;
in
{ config, ... }:
let
cfg = config.deploy;
in
{
options.deploy =
let
genericOptions =
let
mkGenericOption =
type:
mkOption {
type = types.nullOr type;
default = null;
};
in
{
options = {
sshUser = mkGenericOption types.str;
user = mkGenericOption types.str;
sshOpts = mkGenericOption (types.listOf types.str);
fastConnection = mkGenericOption types.bool;
autoRollback = mkGenericOption types.bool;
magicRollback = mkGenericOption types.bool;
confirmTimeout = mkGenericOption types.int;
activationTimeout = mkGenericOption types.int;
tempPath = mkGenericOption types.str;
interactiveSudo = mkGenericOption types.bool;
};
};
profileModule =
{ config, ... }:
{
imports = [ genericOptions ];
options = {
activation = mkOption {
type = types.oneOf [
(types.enum [
"nixos"
"home-manager"
"darwin"
"noop"
])
];
};
closure = mkOption { type = types.raw; };
profilePath = mkOption {
type = types.nullOr types.str;
default = null;
};
path = mkOption {
type = types.raw;
internal = true;
};
};
config =
let
inherit (config.closure.config.nixpkgs.hostPlatform) system;
deploy-rs = deploy-rs-for-system system;
in
lib.mkMerge [
(lib.mkIf (lib.elem config.activation [
"nixos"
"home-manager"
"darwin"
"noop"
]) { path = deploy-rs.lib.activate.${config.activation} config.closure; })
];
};
nodeModule = {
imports = [ genericOptions ];
options = {
hostname = mkOption { type = types.str; };
profilesOrder = mkOption {
type = types.listOf types.str;
default = [ ];
};
profiles = mkOption {
type = types.attrsOf (types.submoduleWith { modules = [ profileModule ]; });
apply = lib.mapAttrs (
_: profile:
lib.filterAttrs (
name: val:
!(lib.elem name [
"activation"
"closure"
])
&& val != null
) profile
);
default = { };
};
};
};
in
{
nodes = mkOption {
default = { };
type = types.attrsOf (types.submoduleWith { modules = [ nodeModule ]; });
apply = lib.mapAttrs (_: node: lib.filterAttrs (_: val: val != null) node);
};
};
config = lib.mkIf (cfg.nodes != { }) { flake.deploy.nodes = cfg.nodes; };
}

View file

@ -1,5 +1,10 @@
{ pkgs }: {
pkgs.lib.packagesFromDirectoryRecursive { perSystem =
{ pkgs, ... }:
{
packages = pkgs.lib.packagesFromDirectoryRecursive {
inherit (pkgs) callPackage; inherit (pkgs) callPackage;
directory = ./packages; directory = ./packages;
};
};
} }