initial import

This commit is contained in:
James Andariese 2022-09-23 22:24:58 -05:00
parent 1869730067
commit a5ece1f3ce
21 changed files with 578 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.*
\#*
*~
result
!.gitignore

7
LICENSE Executable file
View File

@ -0,0 +1,7 @@
Copyright 2022 James Andariese
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

31
README.md Normal file
View File

@ -0,0 +1,31 @@
# `cascade`
The next iteration of the cascade network.
Should it have been thunder?
Probably.
It's cascade anyway.
## Usage
From a [Proxmox VE][PVE] machine with [Nix][NIX] installed:
```bash
nix-shell
```
Guidance will be printed which should be followed.
## Prerequisites
In order to discover your network and configure hosts, this configuration management system requires the following:
* Samba DC with functioning DNS and DHCP server.
* You must be able to auth with Kerberos to Samba.
* Proxmox running on the local host
* Proxmox should have a local filesystem called hdd-fs
NIX: https://www.nixos.org
PVE: https://www.proxmox.com/en/proxmox-ve

37
common/bash-is-needed.nix Normal file
View File

@ -0,0 +1,37 @@
{ config, lib, pkgs, ... }:
with lib;
{
options = {
environment.binbash.enable = mkOption {
default = true;
type = types.bool;
description = ''
Include a /bin/bash in the system.
'';
};
environment.binbash.bash = mkOption {
type = types.package;
default = pkgs.bashInteractive;
defaultText = literalExpression "pkgs.bashInteractive";
example = literalExpression "pkgs.bash";
description = lib.mdDoc ''
The bash implementation that will be present in
`/bin/bash` after enabling this option.
'';
};
};
config = {
system.activationScripts.binbash = if config.environment.binbash.enable
then ''
mkdir -m 0755 -p /bin
ln -sfn ${config.environment.binbash.bash}/bin/bash /bin/.bash.tmp
mv /bin/.bash.tmp /bin/bash # atomically replace /usr/bin/env
''
else ''
rm -f /bin/bash
'';
};
}

View File

@ -0,0 +1,21 @@
{config, lib, ...}: {
options = with lib; with types; {
cascade.bridge-interface = mkOption {
type = str;
description = "interface on which to create primary bridge (br0)";
};
};
config = with lib; {
networking = {
useNetworkd = mkForce true;
bridges.br0.interfaces = [config.cascade.bridge-interface];
interfaces.br0.useDHCP = mkImageMediaOverride true;
};
#systemd.network.links."05-br0".matchConfig.Name = "br0";
#systemd.network.links."05-br0".linkConfig.MACAddressPolicy = "none";
};
}

11
common/default.nix Normal file
View File

@ -0,0 +1,11 @@
let _ = builtins.trace "${toString ./.}/default.nix"; in
{...}:
with builtins;
with import (toString ../functions);
let folder = import-folder {path = "${toString ./.}"; filenameMatch = ".*[.]nix"; filenameBadMatch = ".*_.*";};
goodNames = attrNames folder;
goodPaths = map (n: "${toString ./.}/${n}.nix") goodNames;
in
{ imports = goodPaths; }

7
common/ssh.nix Normal file
View File

@ -0,0 +1,7 @@
{ pkgs, config, ... }: {
config.programs.ssh.package = pkgs.openssh_gssapi;
config.services.openssh.enable = true;
config.networking.firewall.allowedTCPPorts = [ 22 ];
}

24
common/users.nix Normal file
View File

@ -0,0 +1,24 @@
{pkgs, ...}: {
config = {
users.users = rec {
james = {
createHome = true;
description = "James Andariese";
extraGroups = [ "wheel" ];
group = "users";
home = "/home/james";
shell = pkgs.bashInteractive;
uid = 1982;
isNormalUser = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBDEj6S+ISygrn6D7a5GBsrYaUMWjcReyMmrlgRdDUGx james@chimecho"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBW+t2we/HTgV9ykgcQWiHqiA+vEehVhwOcbsLr4jJvL james@Jamess-MBP.cascade.strudelline.net"
];
};
root.shell = james.shell;
root.openssh.authorizedKeys.keys = james.openssh.authorizedKeys.keys;
};
security.sudo.wheelNeedsPassword = false;
nix.settings.trusted-users = [ "root" "@wheel" ];
};
}

View File

@ -0,0 +1,20 @@
{ config, lib, pkgs, ... }:
{
imports = [
./common
];
config = {
system.stateVersion = "22.11";
nix.nixPath = [
"nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/etc/nixos/configuration.nix"
"hardware-config=/etc/nixos/hardware-configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels"
];
}
# also pull in all of the qemu-vm profile which will be the basis of the live config
// (builtins.removeAttrs (import ./profiles/qemu-vm {inherit lib;}) [ "deployment" ]);
}

21
custom-image.nix Normal file
View File

@ -0,0 +1,21 @@
{}:
let
myisoconfig = import ./custom-image-configuration.nix;
copyChannel = true;
system = "x86_64-linux";
evalNixos = configuration: import <nixpkgs/nixos> {
inherit system configuration;
};
resultOfEval = (evalNixos myisoconfig);
lib = resultOfEval.pkgs.lib;
pkgs = resultOfEval.pkgs;
config = resultOfEval.config;
in
import <nixpkgs/nixos/lib/make-disk-image.nix> {
inherit lib copyChannel pkgs config;
diskSize = "20480";
format = "raw";
installBootLoader = true;
partitionTableType = "hybrid";
}

5
functions/default.nix Normal file
View File

@ -0,0 +1,5 @@
let _ = builtins.trace "${toString ./.}/default.nix"; in
let import-folder = import (toString ./import-folder.nix);
in
import-folder {path = (toString ./.);}

View File

@ -0,0 +1,17 @@
with builtins;
let mkIsFilenameAMatch = {filenameMatch ? "[^_].*", filenameBadMatch ? null, ...}@opt:
{name, type, ...}:
if name == "default.nix" then false
else if builtins.match filenameMatch name == null then false
else if filenameBadMatch != null && builtins.match filenameBadMatch name != null then false
else if ! elem type ["symlink" "regular"] then false
else if builtins.match ".*[.]nix" name == null then false
else true;
readDirItems = import ./readDirItems.nix;
extractName = fn: let m = match "(.*[/])?([a-zA-Z0-9-]+)[.]nix" fn; in if m == null then throw "${fn} does not seem to have a correct filename" else head (tail m);
matches = {path, ...}@opt:
let isFilenameAMatch = mkIsFilenameAMatch opt;
in
listToAttrs (map ({name,...}: let pname = name; in {name = "${extractName name}"; value = import "${path}/${name}";}) (filter isFilenameAMatch (readDirItems "${path}" )));
in
matches

View File

@ -0,0 +1,3 @@
with builtins;
kname: vname: aset: attrValues (mapAttrs (k: v: {"${kname}" = k; "${vname}" = v;}) aset)

View File

@ -0,0 +1,3 @@
with builtins;
p: (import ./mkAttrItemsFunction.nix) "name" "type" (readDir p)

8
hosts/_basic.nix Normal file
View File

@ -0,0 +1,8 @@
{ nodes, config, pkgs, ... }: {
imports = [
(toString ../profiles/qemu-vm)
];
config = {
deployment.tags = [ ];
};
}

5
hosts/default.nix Normal file
View File

@ -0,0 +1,5 @@
let _ = builtins.trace "${toString ./.}/default.nix"; in
with builtins;
with import (toString ../functions);
import-folder {path = "${toString ./.}"; filenameMatch = ".*[.]nix"; filenameBadMatch = ".*_.*";}

13
network.nix Normal file
View File

@ -0,0 +1,13 @@
with builtins;
let pkgs = import <nixpkgs> {};
network = {
inherit pkgs;
description = "cascade";
};
hosts = import (toString ./hosts);
in
hosts // {
inherit network;
}

48
profiles/api/default.nix Normal file
View File

@ -0,0 +1,48 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (toString ../base) ];
config = {
deployment.targetUser = "root";
boot.initrd.availableKernelModules = [ "xhci_pci" "dwc3_pci" "usbhid" "usb_storage" "uas" "sd_mod" "sdhci_acpi" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/mmcblk1p1";
fsType = "xfs";
};
fileSystems."/boot" =
{ device = "/dev/mmcblk1p3";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/mmcblk1p2"; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.interfaces.wlp0s20u3.useDHCP = lib.mkDefault false;
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
# high-resolution display
hardware.video.hidpi.enable = lib.mkDefault true;
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
#networking.meth.meth0.replaceInterface = "enp1s0";
#
cascade.bridge-interface = "enp1s0";
};
}

14
profiles/base/default.nix Normal file
View File

@ -0,0 +1,14 @@
{pkgs,...}:
{
imports = [
(toString ../../common)
];
config = {
environment.systemPackages = with pkgs; [ bridge-utils ];
programs.neovim.enable = true;
programs.neovim.vimAlias = true;
programs.neovim.viAlias = true;
system.stateVersion = "22.11";
};
}

View File

@ -0,0 +1,51 @@
{lib, ...}:
with lib;
{
imports = [ (toString ../base) ];
config = {
deployment.targetUser = "root";
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
autoResize = true;
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
swapDevices = [
{device = "/swap"; size = 1024;} # make sure we always have enough memory to rebuild nixos.
];
# boot.initrd.network.enable = true;
# networking.useDHCP = true;
networking.useNetworkd = mkForce true;
networking.networkmanager.enable = false;
#networking.interfaces.ens18.useDHCP = false;
#networking.bridges.br0.interfaces = [ "ens18" ];
#networking.interfaces.br0.useDHCP = lib.mkDefault true;
# hardware.cpu.amd.updateMicrocode = lib.config.hardware.enableRedistributableFirmware;
boot.initrd.availableKernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" ];
boot.initrd.kernelModules = [ "virtio_balloon" "virtio_console" "virtio_rng" ];
boot.growPartition = true;
boot.kernelParams = [ "console=ttyS0" ];
boot.loader.grub.device = "/dev/vda";
boot.loader.timeout = mkDefault 3;
boot.consoleLogLevel = 3;
boot.initrd.verbose = true;
services.getty.autologinUser = "root";
services.sshd.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
services.qemuGuest.enable = true;
cascade.bridge-interface = "ens18";
};
}

227
shell.nix Executable file
View File

@ -0,0 +1,227 @@
{ pkgs ? import <nixpkgs> {}, ... }:
let
vault_addr = "http://vault:8200";
in
with pkgs;
with lib;
stdenv.mkDerivation {
name = "commands-nix";
buildInputs = [ terraform vault coreutils-full dig bash samba4Full morph ];
shellHook = ''
export NIX_PATH="nixpkgs=${toString <nixpkgs>}"
export LD_LIBRARY_PATH="${libvirt}/lib:$LD_LIBRARY_PATH"
export VAULT_ADDR=${escapeShellArg vault_addr}
export DOMAIN="$(hostname -d)"
MEMORY=2048
name="$1"
rebuild-nixos-image() {
echo "$(nix-build \
-I nixos-config=custom-image-configuration.nix \
-I hardware-config=profiles/qemu-vm/default.nix \
custom-image.nix)/nixos".img
}
mkvirt() {
eval "$(env_cascade)" || return 3
eval "$(env_name "$1")" || return 4
if [ -f "./hosts/$name"".nix" ];then
1>&2 echo "there is already a file at ./hosts/$name"".nix. move it or remove it."
return 1
fi
cp "./hosts/_basic.nix" "./hosts/""$name"".nix"
morph build network.nix --on="$name" &
[ -f "result/nixos.img" ] || (
1>&2 echo "you do not seem to have a result/nixos.img file. building one now."
rebuild-nixos-image || errcho "could not build result/nixos.img." || return $?
)
[ -f "result/nixos.img" ] || errcho "there must be a file in result/nixos.img. make one with rebuild-nixos-image." || return $?
IMG="$(realpath result/nixos.img)"
[ -f "$IMG" ] || errcho "result/nixos.img must be resolveable to a file. rebuild the image link with rebuild-nixos-image." || return $?
[ $? -eq 0 ] || return 5
VMID=$(sudo pvesh get /cluster/nextid)
[ $? -eq 0 ] || return 6
sudo qm create $VMID \
--memory "$MEMORY" \
--net0 virtio,bridge=vmbr0 \
--ipconfig0 ip=dhcp \
--agent enabled=1,type=virtio \
--virtio0 hdd-fs:0,import-from="$IMG",discard=on,format=raw \
--boot c \
--bootdisk virtio0 \
--vga serial0 \
--serial0 socket \
--name "$name" \
--start 1
[ $? -eq 0 ] || return 7
IP="$(wait-for-vm-ipv4 $VMID)"
[ $? -eq 0 ] || return 8
samba-create-ipv4-records "$IP" "$name" "$DOMAIN"
[ $? -eq 0 ] || return 9
1>&2 echo "removing any stale ssh keys"
ssh-keygen -f "/home/james/.ssh/known_hosts" -R "$name"
ssh-keygen -f "/home/james/.ssh/known_hosts" -R "$name.$DOMAIN"
ssh-keygen -f "/home/james/.ssh/known_hosts" -R "$IP"
1>&2 echo "scanning for new ssh keys"
# head -1 allows us to grab the ed25519 key before the rsa key.
# at worst, it will yield a single key of some sort which is also fine.
(
echo
ssh-keyscan "$IP" | sort | head -1
echo
ssh-keyscan "$name" | sort | head -1
echo
ssh-keyscan "$name.$DOMAIN" | sort | head -1
echo
) 2> /dev/null >> ~/.ssh/known_hosts
1>&2 echo "waiting for morph build to finish"
wait
1>&2 echo "morphing host"
morph deploy network.nix switch --on="$name"
}
samba-create-ipv4-records() {
eval "$(env_cascade)"
IP="$1"
HOST="$2"
:;:;:;:;:;:;:;:;: ;_IP_R="$IP"
IP1="''${_IP_R%%.*}";_IP_R="''${_IP_R#*.}"
IP2="''${_IP_R%%.*}";_IP_R="''${_IP_R#*.}"
IP3="''${_IP_R%%.*}";_IP_R="''${_IP_R#*.}"
IP4="''${_IP_R}" ;_IP_R="''${_IP_R#*.}"
# at the end, the last fragment has no . so if we try to remove another dotted-quad/4,
# we simply get no changes. the _desired end state_ is to have _IP_R = IP4.
[ x"$IP4" = x"$_IP_R" ] || errcho "error parsing IPv4"
samba-tool dns cleanup -k yes "$DC" "$HOST"."$DOMAIN"
samba-tool dns add -k yes "$DC" "$DOMAIN" "$HOST" A "$IP"
samba-tool dns zonecreate "$DC" "$IP3.$IP2.$IP1.in-addr.arpa." -k yes || true
samba-tool dns add -k yes "$DC" "$IP3.$IP2.$IP1.in-addr.arpa." "$IP4.$IP3.$IP2.$IP1.in-addr.arpa." PTR "$HOST.$DOMAIN."
}
wait-for-vm-ipv4() {
set -x
VMID="$1"
while true;do
IP="$( sudo qm guest cmd $VMID network-get-interfaces | \
jq -r '
.[]
|select(.["hardware-address"] != "00:00:00:00:00:00")
|.["ip-addresses"] | select(.) | .[]
|select(.["ip-address-type"] == "ipv4")
|.["ip-address"]' \
2> /dev/null \
| grep -E '172|10|192' || true)"
if [ x != x"$IP" ];then
echo "$IP"
break
fi
sleep 1
done
set +x
}
env_cascade() {
DOMAIN="$(hostname -d)"
#calculate some other variables
REALM="$(echo "$DOMAIN" | tr a-z A-Z)"
WORKGROUP="$(echo "$DOMAIN" | cut -d . -f 1)"
#and recalculate others to fix caps
DOMAIN="$(echo "$REALM" | tr A-Z a-z)"
DC="$(dig +short -x "$(dig +short "$DOMAIN")")"
DC="''${DC%.}" # just in case, let's drop the absolution dot
workgroup="$(echo "$WORKGROUP" | tr A-Z a-z)"
WORKGROUP="$(echo "$workgroup" | tr a-z A-Z)"
printf '%q=%q\n' \
DOMAIN "$DOMAIN" \
REALM "$REALM" \
WORKGROUP "$WORKGROUP" \
DC "$DC" \
workgroup "$workgroup"
}
env_name() {
# protect thy stdout
(
exec 18>&1-
exec 1>&2
NAME="$1"
[ x"$NAME" != x ] || errcho "you need to provide a hostname"
exec 1>&18
printf '%q=%q\n' name "$(echo "$NAME" | tr A-Z a-z)"
printf '%q=%q\n' NAME "$(echo "$NAME" | tr a-z A-Z)"
)
}
errcho() {
RC=$?
if [ $RC -ne 0 ];then
1>&2 printf '%s\n' "$@"
return $RC
fi
}
guestbash() {
VMID="$1"
CMD="$2"
shift;shift
echo -n "$CMD" | sudo qm guest exec --pass-stdin=1 --timeout=300 "$VMID" -- /run/wrappers/bin/sudo -i bash -s "$@"
}
deploy() {
nix-build network.nix && morph build network.nix && morph deploy network.nix "$@"
}
destroy-host() {
while [ $# -gt 0 ];do
VMID="$(sudo qm list | awk -v N="$1" '$2 == N {print $1}')"
echo "destroying hosts/$1.nix and VM #$VMID" 1>&2
sleep 1
sudo qm stop $VMID
sudo qm destroy $VMID
rm -f hosts/"$1".nix
shift
done
}
cat << EOF
Welcome! You've entered the cascade shell.
You may make a virt on the local proxmox host using:
mkvirt hostname-here
You may also register an existing host in DNS using:
samba-create-ipv4-records IP hostname-here
Then morph may be used to deploy:
# Create a hosts/hostname-here.nix file based on hosts/basic.nix.sample
deploy test # this is an alias which runs morph on network.nix.
deploy switch # the argument is the same as to morph deploy and nixos-rebuild
EOF
'';
}