Compare commits

..

26 Commits

Author SHA1 Message Date
Ryan Yin 0693713e94 feat: list systemd services 2024-08-17 20:40:27 +08:00
Ryan Yin 1bf67acde9 fix: hdd storage - auto unlock after booted (#158) 2024-08-17 04:05:22 +08:00
Ryan Yin f1a5d419fb fix: hdd storage - do not unlock on boot, increase boot.loader.timeout 2024-08-17 03:06:37 +08:00
Ryan Yin e8be41f8e1 Merge pull request #156 from ryan4yin/data-crypted
feat: data-crypted
2024-08-17 02:28:12 +08:00
Ryan Yin d853036fb1 feat: data-crypted 2024-08-17 02:26:39 +08:00
Ryan Yin df1f9b0070 chore: remove sbc's just commands 2024-08-16 15:13:21 +08:00
Ryan Yin 4def213b08 Merge pull request #155 from ryan4yin/remove-sbcs
refactor: migrate sbcs to https://github.com/ryan4yin/nixos-config-sbc
2024-08-16 15:08:12 +08:00
Ryan Yin 35eb6ed5c9 refactor: migrate sbcs to https://github.com/ryan4yin/nixos-config-sbc 2024-08-16 15:06:24 +08:00
Ryan Yin 07178984b1 Merge pull request #154 from ryan4yin/refactor-vars
refactor: migrate ssh auth keys & hashed password into vars
2024-08-16 14:40:54 +08:00
Ryan Yin eb83e88267 refactor: migrate ssh auth keys & hashed password into vars 2024-08-16 14:40:06 +08:00
Ryan Yin 6c8e8111c6 docs: dae - comment 2024-08-16 11:46:37 +08:00
Ryan Yin 62e96cde7e feat: dae - avoid rate limit of GitHub API & Docker Hub API 2024-08-16 11:44:30 +08:00
Ryan Yin 4bb53d0190 feat: justfile - gc 2024-08-15 22:50:20 +08:00
Ryan Yin 41af2c1444 Merge pull request #152 from ryan4yin/kubevirt-hugepages
feat: kubevirt - use 75% of the memory for hugepages
2024-08-15 22:50:05 +08:00
Ryan Yin 43db6bcf63 feat: kubevirt - use 75% of the memory for hugepages 2024-08-15 17:19:26 +08:00
Ryan Yin e17bc1ec23 feat: neovim - tree-sitter - just 2024-08-12 18:30:26 +08:00
Ryan Yin c9954c009a chore: justfile - gc - delete old than 7 days 2024-08-12 17:56:46 +08:00
Ryan Yin e68a43edce docs: justfile - nix gc 2024-08-12 17:51:02 +08:00
Ryan Yin 08a6885873 feat: justfile - nix gc 2024-08-12 17:44:37 +08:00
Ryan Yin 930d8322d1 feat: use the latest version of neovim 2024-08-12 17:18:59 +08:00
Ryan Yin 2363ab59c4 chore: add default to just 2024-08-12 17:18:18 +08:00
Ryan Yin 1394e26a59 chore: update flake.lock 2024-08-12 17:18:02 +08:00
Ryan Yin 940367c790 chore: update scripts 2024-08-12 15:17:38 +08:00
Ryan Yin cae48ede1b fix: update ntp time servers 2024-08-11 20:18:20 +08:00
Ryan Yin 9535c09a33 feat: add kubectx 2024-08-06 15:13:54 +08:00
Ryan Yin 194c3d9895 chore: remove some unused just commands 2024-08-01 10:14:54 +08:00
61 changed files with 275 additions and 3172 deletions
+29 -74
View File
@@ -14,6 +14,10 @@ utils_nu := absolute_path("utils.nu")
#
############################################################################
# List all the just commands
default:
@just --list
# Run eval tests
[group('nix')]
test:
@@ -41,6 +45,7 @@ repl:
nix repl -f flake:nixpkgs
# remove all generations older than 7 days
# on darwin, you may need to switch to root user to run this command
[group('nix')]
clean:
sudo nix profile wipe-history --profile /nix/var/nix/profiles/system --older-than 7d
@@ -48,8 +53,11 @@ clean:
# Garbage collect all unused nix store entries
[group('nix')]
gc:
# garbage collect all unused nix store entries
sudo nix-collect-garbage --delete-old
# garbage collect all unused nix store entries(system-wide)
sudo nix-collect-garbage --delete-older-than 7d
# garbage collect all unused nix store entries(for the user - home-manager)
# https://github.com/LnL7/nix-darwin/issues/237
nix-collect-garbage --delete-older-than 7d
# Enter a shell session which has all the necessary tools for this flake
[linux]
@@ -68,6 +76,11 @@ fmt:
# format the nix files in this repo
nix fmt
# Show all the auto gc roots in the nix store
[group('nix')]
gcroot:
ls -al /nix/var/nix/gcroots/auto/
############################################################################
#
# NixOS Desktop related commands
@@ -277,7 +290,7 @@ kana-local mode="default":
# Build and upload a vm image
[linux]
[group('homelab')]
upload-k3s mode="default":
upload-k3s-prod mode="default":
#!/usr/bin/env nu
use {{utils_nu}} *;
upload-vm k3s-prod-1-master-1 {{mode}};
@@ -298,82 +311,14 @@ upload-k3s-test mode="default":
[linux]
[group('homelab')]
k3s:
colmena apply --on '@k3s-*' --verbose --show-trace
[linux]
[group('homelab')]
master:
colmena apply --on '@k3s-prod-1-master-*' --verbose --show-trace
[linux]
[group('homelab')]
worker:
colmena apply --on '@k3s-prod-1-worker-*' --verbose --show-trace
k3s-prod:
colmena apply --on '@k3s-prod-*' --verbose --show-trace
[linux]
[group('homelab')]
k3s-test:
colmena apply --on '@k3s-test-*' --verbose --show-trace
############################################################################
#
# RISC-V related commands
#
############################################################################
[linux]
[group('homelab')]
riscv:
colmena apply --on '@riscv' --verbose --show-trace
[linux]
[group('homelab')]
nozomi:
colmena apply --on '@nozomi' --verbose --show-trace
[linux]
[group('homelab')]
yukina:
colmena apply --on '@yukina' --verbose --show-trace
############################################################################
#
# Aarch64 related commands
#
############################################################################
[linux]
[group('homelab')]
rakushun:
colmena apply --on '@rakushun' --build-on-target --verbose --show-trace
[linux]
[group('homelab')]
rakushun-local mode="default":
#!/usr/bin/env nu
use {{utils_nu}} *;
nixos-switch rakushun {{mode}}
[linux]
[group('homelab')]
suzu-set-proxy:
ip route del default via 192.168.5.1
ip route add default via 192.168.5.178
[linux]
[group('homelab')]
suzu-unset-proxy:
ip route del default via 192.168.5.178
ip route add default via 192.168.5.1
[linux]
[group('homelab')]
suzu-local mode="default":
#!/usr/bin/env nu
use {{utils_nu}} *;
nixos-switch suzu {{mode}}
############################################################################
#
# Neovim related commands
@@ -457,4 +402,14 @@ game:
# Delete all failed pods
[group('k8s')]
del-failed:
kubectl delete pod --all-namespaces --field-selector="status.phase==Failed"
kubectl delete pod --all-namespaces --field-selector="status.phase==Failed"
[linux]
[group('services')]
list-inactive:
systemctl list-units -all --state=inactive
[linux]
[group('services')]
list-failed:
systemctl list-units -all --state=failed
Generated
+95 -656
View File
File diff suppressed because it is too large Load Diff
-15
View File
@@ -101,19 +101,11 @@
nuenv.url = "github:DeterminateSystems/nuenv";
daeuniverse.url = "github:daeuniverse/flake.nix";
# daeuniverse.url = "github:daeuniverse/flake.nix/exp";
haumea = {
url = "github:nix-community/haumea/v0.2.2";
inputs.nixpkgs.follows = "nixpkgs";
};
microvm = {
url = "github:astro/microvm.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
######################## Some non-flake repositories #########################################
# doom-emacs is a configuration framework for GNU Emacs.
@@ -146,12 +138,5 @@
url = "github:ryan4yin/nur-packages";
# inputs.nixpkgs.follows = "nixpkgs";
};
# riscv64 SBCs
nixos-licheepi4a.url = "github:ryan4yin/nixos-licheepi4a";
# nixos-jh7110.url = "github:ryan4yin/nixos-jh7110";
# aarch64 SBCs
nixos-rk3588.url = "github:ryan4yin/nixos-rk3588";
};
}
+1
View File
@@ -12,6 +12,7 @@
go-containerregistry # provides `crane` & `gcrane`, it's similar to skopeo
kubectl
kubectx
kubebuilder
istioctl
clusterctl # for kubernetes cluster-api
+2
View File
@@ -2,6 +2,7 @@
config,
lib,
pkgs,
pkgs-unstable,
...
}:
###############################################################################
@@ -25,6 +26,7 @@ in {
programs = {
neovim = {
enable = true;
package = pkgs-unstable.neovim-unwrapped;
defaultEditor = true;
viAlias = true;
@@ -24,6 +24,7 @@ return {
opts.ensure_installed = require("astrocore").list_insert_unique(opts.ensure_installed, {
-- please add only the tree-sitters that are not available in nixpkgs here
"just",
"kdl",
"csv",
"xml",
@@ -1,157 +0,0 @@
# Rakushun - Disk and Installation
Disk layout:
```bash
[ryan@rakushun:~]$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 58.6G 0 disk
└─sda1 8:1 1 487M 0 part
mtdblock0 31:0 0 16M 0 disk
zram0 254:0 0 0B 0 disk
nvme0n1 259:0 0 1.8T 0 disk
├─nvme0n1p1 259:1 0 630M 0 part /boot
└─nvme0n1p2 259:2 0 1.8T 0 part
└─encrypted 253:0 0 1.8T 0 crypt /tmp
/swap
/snapshots
/home/ryan/tmp
/home/ryan/nix-config
/home/ryan/go
/home/ryan/codes
/home/ryan/.ssh
/home/ryan/.local/state
/home/ryan/.npm
/home/ryan/.local/share
/home/ryan/.conda
/etc/ssh
/etc/nix/inputs
/etc/secureboot
/etc/agenix
/etc/NetworkManager/system-connections
/etc/machine-id
/nix/store
/var/log
/var/lib
/nix
/persistent
[ryan@rakushun:~]$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 785M 0 785M 0% /dev
tmpfs tmpfs 7.7G 0 7.7G 0% /dev/shm
tmpfs tmpfs 3.9G 6.8M 3.9G 1% /run
tmpfs tmpfs 7.7G 1.9M 7.7G 1% /run/wrappers
none tmpfs 4.0G 48K 4.0G 1% /
/dev/mapper/crypted btrfs 1.9T 19G 1.8T 2% /persistent
/dev/mapper/crypted btrfs 1.9T 19G 1.8T 2% /nix
/dev/mapper/crypted btrfs 1.9T 19G 1.8T 2% /snapshots
/dev/mapper/crypted btrfs 1.9T 19G 1.8T 2% /swap
/dev/mapper/crypted btrfs 1.9T 19G 1.8T 2% /tmp
/dev/nvme0n1p1 vfat 629M 96M 534M 16% /boot
tmpfs tmpfs 1.6G 4.0K 1.6G 1% /run/user/1000
```
CPU info:
```bash
[ryan@rakushun:~]$ lscpu
Architecture: aarch64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: ARM
Model name: Cortex-A55
Model: 0
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
Stepping: r2p0
CPU(s) scaling MHz: 67%
CPU max MHz: 1800.0000
CPU min MHz: 408.0000
BogoMIPS: 48.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
Model name: Cortex-A76
Model: 0
Thread(s) per core: 1
Core(s) per socket: 2
Socket(s): 2
Stepping: r4p0
CPU(s) scaling MHz: 18%
CPU max MHz: 2256.0000
CPU min MHz: 408.0000
BogoMIPS: 48.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
Caches (sum of all):
L1d: 384 KiB (8 instances)
L1i: 384 KiB (8 instances)
L2: 2.5 MiB (8 instances)
L3: 3 MiB (1 instance)
```
## How to install NixOS on Orange Pi 5 Plus
### 1. Prepare a USB LUKS key
Generate LUKS keyfile to encrypt the root partition, it's used by disko.
```bash
# partition the usb stick
DEV=/dev/sdX
parted ${DEV} -- mklabel gpt
parted ${DEV} -- mkpart OPI5P_DSC fat32 0% 512MB
mkfs.fat -F 32 -n OPI5P_DSC ${DEV}1
# Generate a keyfile from the true random number generator
KEYFILE=./orangepi5plus-luks-keyfile
dd bs=512 count=64 iflag=fullblock if=/dev/random of=$KEYFILE
# copy the keyfile and token to the usb stick
KEYFILE=./orangepi5plus-luks-keyfile
DEVICE=/dev/disk/by-label/OPI5P_DSC
# seek=128 skip N obs-sized output blocks to avoid overwriting the filesystem header
dd bs=512 count=64 iflag=fullblock seek=128 if=$KEYFILE of=$DEVICE
```
### 2. Partition the SSD & install NixOS via disko
First, follow
[UEFI - ryan4yin/nixos-rk3588](https://github.com/ryan4yin/nixos-rk3588/blob/main/UEFI.md) to
install UEFI bootloader and boot into NixOS live environment via a USB stick.
Then, run the following commands:
```bash
# transfer the nix-config to the target machine
rsync -avzP ~/nix-config rk@<ip-addr>:/home/rk/
# login via ssh
ssh rk@<ip-addr>
cd ~/nix-config/hosts/12kingdoms_rakushun
# 1. change the disk device path in ./disko-fs.nix to the disk you want to use
# 2. partition & format the disk via disko
sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko -- --mode disko ./disko-fs.nix
cd ~/nix-config
# install nixos
sudo nixos-install --root /mnt --flake .#rakushun --no-root-password --show-trace --verbose
# enter into the installed system, check password & users
# `su ryan` => `sudo -i` => enter ryan's password => successfully login
# if login failed, check the password you set in install-1, and try again
nixos-enter
# NOTE: DO NOT skip this step!!!
# copy the essential files into /persistent
# otherwise the / will be cleared and data will lost
## NOTE: impermanence just create links from / to /persistent
## We need to copy files into /persistent manually!!!
mv /etc/machine-id /persistent/etc/
mv /etc/ssh /persistent/etc/
mkdir -p /persistent/home/ryan
chown -R ryan:ryan /persistent/home/ryan
```
-16
View File
@@ -1,16 +0,0 @@
# Rakushun - Orange Pi 5 Plus
LUKS encrypted SSD for NixOS, on Orange Pi 5 Plus.
TODO
## Showcases
![](../../_img/2024-03-07_orangepi5plus_rakushun.webp)
## Misc
```bash
# copy closure to another arm64 machine
nix-copy-closure --to root@suzu /run/current-system
```
-38
View File
@@ -1,38 +0,0 @@
{
mylib,
disko,
nixos-rk3588,
myvars,
...
}:
#############################################################
#
# Suzu - Orange Pi 5 Plus, RK3588 + 16GB RAM
#
#############################################################
let
hostName = "rakushun"; # Define your hostname.
in {
imports =
(mylib.scanPaths ./.)
++ [
# import the rk3588 module, which contains the configuration for bootloader/kernel/firmware
nixos-rk3588.nixosModules.orangepi5plus.core
disko.nixosModules.default
];
networking = {
inherit hostName;
inherit (myvars.networking) defaultGateway nameservers;
inherit (myvars.networking.hostsInterface.${hostName}) interfaces;
networkmanager.enable = false;
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "24.05"; # Did you read the comment?
}
-111
View File
@@ -1,111 +0,0 @@
{
# required by impermanence
fileSystems."/persistent".neededForBoot = true;
disko.devices = {
nodev."/" = {
fsType = "tmpfs";
mountOptions = [
"size=4G"
"defaults"
# set mode to 755, otherwise systemd will set it to 777, which cause problems.
# relatime: Update inode access times relative to modify or change time.
"mode=755"
];
};
# TODO: rename to main
disk.sda = {
type = "disk";
# When using disko-install, we will overwrite this value from the commandline
device = "/dev/nvme0n1"; # The device to partition
content = {
type = "gpt";
partitions = {
# The EFI & Boot partition
ESP = {
size = "630M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [
"defaults"
];
};
};
# The root partition
luks = {
size = "100%";
content = {
type = "luks";
name = "encrypted";
settings = {
keyFile = "/dev/disk/by-label/OPI5P_DSC"; # The keyfile is stored on a USB stick
# The maximum size of the keyfile is 8192 KiB
# type `cryptsetup --help` to see the compiled-in key and passphrase maximum sizes
keyFileSize = 512 * 64; # match the `bs * count` of the `dd` command
keyFileOffset = 512 * 128; # match the `bs * skip` of the `dd` command
fallbackToPassword = true;
allowDiscards = true;
};
# Whether to add a boot.initrd.luks.devices entry for the specified disk.
initrdUnlock = true;
# encrypt the root partition with luks2 and argon2id, will prompt for a passphrase, which will be used to unlock the partition.
# cryptsetup luksFormat
extraFormatArgs = [
"--type luks2"
"--cipher aes-xts-plain64"
"--hash sha512"
"--iter-time 5000"
"--key-size 256"
"--pbkdf argon2id"
# use true random data from /dev/random, will block until enough entropy is available
"--use-random"
];
extraOpenArgs = [
"--timeout 10"
];
content = {
type = "btrfs";
extraArgs = ["-f"]; # Force override existing partition
subvolumes = {
# mount the top-level subvolume at /btr_pool
# it will be used by btrbk to create snapshots
"/" = {
mountpoint = "/btr_pool";
# btrfs's top-level subvolume, internally has an id 5
# we can access all other subvolumes from this subvolume.
mountOptions = ["subvolid=5"];
};
"@nix" = {
mountpoint = "/nix";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@persistent" = {
mountpoint = "/persistent";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@tmp" = {
mountpoint = "/tmp";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@snapshots" = {
mountpoint = "/snapshots";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@swap" = {
mountpoint = "/swap";
swap.swapfile.size = "16384M";
};
};
};
};
};
};
};
};
};
}
@@ -1,39 +0,0 @@
{
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.loader = {
# depending on how you configured your disk mounts, change this to /boot or /boot/efi.
efi.efiSysMountPoint = "/boot/";
efi.canTouchEfiVariables = true;
# do not use systemd-boot here, it has problems when running `nixos-install`
grub = {
device = "nodev";
efiSupport = true;
};
};
# clear /tmp on boot to get a stateless /tmp directory.
boot.tmp.cleanOnBoot = true;
boot.initrd.availableKernelModules = ["nvme" "usbhid" "usb_storage"];
boot.initrd.kernelModules = [];
boot.kernelModules = [];
boot.extraModulePackages = [];
# 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.useDHCP = lib.mkDefault true;
# networking.interfaces.enP3p49s0.useDHCP = lib.mkDefault true;
# networking.interfaces.enP4p65s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
}
@@ -1,75 +0,0 @@
{
impermanence,
pkgs,
...
}: {
imports = [
impermanence.nixosModules.impermanence
];
environment.systemPackages = [
# `sudo ncdu -x /`
pkgs.ncdu
];
# There are two ways to clear the root filesystem on every boot:
## 1. use tmpfs for /
## 2. (btrfs/zfs only)take a blank snapshot of the root filesystem and revert to it on every boot via:
## boot.initrd.postDeviceCommands = ''
## mkdir -p /run/mymount
## mount -o subvol=/ /dev/disk/by-uuid/UUID /run/mymount
## btrfs subvolume delete /run/mymount
## btrfs subvolume snapshot / /run/mymount
## '';
#
# See also https://grahamc.com/blog/erase-your-darlings/
# NOTE: impermanence only mounts the directory/file list below to /persistent
# If the directory/file already exists in the root filesystem, you should
# move those files/directories to /persistent first!
environment.persistence."/persistent" = {
# sets the mount option x-gvfs-hide on all the bind mounts
# to hide them from the file manager
hideMounts = true;
directories = [
"/etc/NetworkManager/system-connections"
"/etc/ssh"
"/etc/nix/inputs"
"/etc/secureboot" # lanzaboote - secure boot
# my secrets
"/etc/agenix/"
"/var/log"
"/var/lib"
];
files = [
"/etc/machine-id"
];
# the following directories will be passed to /persistent/home/$USER
users.ryan = {
directories = [
"codes"
"nix-config"
"tmp"
{
directory = ".ssh";
mode = "0700";
}
# neovim / remmina / flatpak / ...
".local/share"
".local/state"
# language package managers
".npm"
".conda" # generated by `conda-shell`
"go"
];
files = [
".config/nushell/history.txt"
];
};
};
}
@@ -1,156 +0,0 @@
# Suzu - Disk and Installation
Disk layout:
```bash
[ryan@suzu:~]$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 58.6G 0 disk
└─sda1 8:1 1 486M 0 part
mtdblock0 31:0 0 16M 0 disk
zram0 254:0 0 0B 0 disk
nvme0n1 259:0 0 238.5G 0 disk
├─nvme0n1p1 259:1 0 630M 0 part /boot
└─nvme0n1p2 259:2 0 237.9G 0 part
└─encrypted 253:0 0 237.8G 0 crypt /tmp
/snapshots
/swap
/home/ryan/tmp
/home/ryan/nix-config
/home/ryan/go
/home/ryan/.local/state
/home/ryan/codes
/home/ryan/.npm
/home/ryan/.ssh
/home/ryan/.local/share
/etc/ssh
/home/ryan/.conda
/etc/secureboot
/etc/agenix
/etc/nix/inputs
/etc/NetworkManager/system-connections
/nix/store
/var/log
/var/lib
/nix
/persistent
[ryan@suzu:~]$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 383M 0 383M 0% /dev
tmpfs tmpfs 3.8G 0 3.8G 0% /dev/shm
tmpfs tmpfs 1.9G 6.2M 1.9G 1% /run
tmpfs tmpfs 3.8G 1.9M 3.8G 1% /run/wrappers
none tmpfs 2.0G 48K 2.0G 1% /
/dev/mapper/crypted btrfs 238G 11G 226G 5% /persistent
/dev/mapper/crypted btrfs 238G 11G 226G 5% /nix
/dev/mapper/crypted btrfs 238G 11G 226G 5% /swap
/dev/mapper/crypted btrfs 238G 11G 226G 5% /snapshots
/dev/mapper/crypted btrfs 238G 11G 226G 5% /tmp
/dev/nvme0n1p1 vfat 629M 86M 543M 14% /boot
tmpfs tmpfs 766M 4.0K 766M 1% /run/user/1000
```
CPU info:
```bash
[ryan@suzu:~]$ lscpu
Architecture: aarch64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: ARM
Model name: Cortex-A55
Model: 0
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
Stepping: r2p0
CPU(s) scaling MHz: 56%
CPU max MHz: 1800.0000
CPU min MHz: 408.0000
BogoMIPS: 48.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
Model name: Cortex-A76
Model: 0
Thread(s) per core: 1
Core(s) per socket: 2
Socket(s): 2
Stepping: r4p0
CPU(s) scaling MHz: 18%
CPU max MHz: 2256.0000
CPU min MHz: 408.0000
BogoMIPS: 48.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
Caches (sum of all):
L1d: 384 KiB (8 instances)
L1i: 384 KiB (8 instances)
L2: 2.5 MiB (8 instances)
L3: 3 MiB (1 instance)
```
## How to install NixOS on Orange Pi 5
### 1. Prepare a USB LUKS key
Generate LUKS keyfile to encrypt the root partition, it's used by disko.
```bash
# partition the usb stick
DEV=/dev/sdX
parted ${DEV} -- mklabel gpt
parted ${DEV} -- mkpart primary 2M 512MB
mkfs.fat -F 32 -n OPI5_DSC ${DEV}1
# Generate a keyfile from the true random number generator
KEYFILE=./orangepi5-luks-keyfile
dd bs=512 count=64 iflag=fullblock if=/dev/random of=$KEYFILE
# copy the keyfile and token to the usb stick
KEYFILE=./orangepi5-luks-keyfile
DEVICE=/dev/disk/by-label/OPI5_DSC
# seek=128 skip N obs-sized output blocks to avoid overwriting the filesystem header
dd bs=512 count=64 iflag=fullblock seek=128 if=$KEYFILE of=$DEVICE
```
### 2. Partition the SSD & install NixOS via disko
First, follow
[UEFI - ryan4yin/nixos-rk3588](https://github.com/ryan4yin/nixos-rk3588/blob/main/UEFI.md) to
install UEFI bootloader and boot into NixOS live environment via a USB stick.
Then, run the following commands:
```bash
# login via ssh
ssh rk@<ip-addr>
git clone https://github.com/ryan4yin/nix-config.git
cd ~/nix-config/hosts/12kingdoms_suzu
# 1. change the disk device path in ./disko-fs.nix to the disk you want to use
# 2. partition & format the disk via disko
sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko -- --mode disko ./disko-fs.nix
cd ~/nix-config
# install nixos
sudo nixos-install --root /mnt --flake .#suzu --no-root-password --show-trace --verbose
# enter into the installed system, check password & users
# `su ryan` => `sudo -i` => enter ryan's password => successfully login
# if login failed, check the password you set in install-1, and try again
nixos-enter
# NOTE: DO NOT skip this step!!!
# copy the essential files into /persistent
# otherwise the / will be cleared and data will lost
## NOTE: impermanence just create links from / to /persistent
## We need to copy files into /persistent manually!!!
mv /etc/machine-id /persistent/etc/
mv /etc/ssh /persistent/etc/
mkdir -p /persistent/home/ryan
chown -R ryan:ryan /persistent/home/ryan
```
-34
View File
@@ -1,34 +0,0 @@
# Suzu - Orange Pi 5
LUKS encrypted SSD for NixOS, on Orange Pi 5.
## TODOs
- [ ] Add support for BGP routing.
- [Comparing Open Source BGP Stacks](https://elegantnetwork.github.io/posts/comparing-open-source-bgp-stacks/)
- [`services.frr.*` - search.nixos.org](https://search.nixos.org/options?channel=unstable&query=services.frr)
## Showcases
![](../../_img/2024-03-07_orangepi5_suzu.webp)
## Features
Micro VMs:
1. suzi: dae router(transparent proxy, dhcp)
1. mitsuha: tailscale gateway(sub router)
Services:
1. OCI Containers: to run some servides that's not available in NixOS.
1. ddns
1. uptime-kuma: uptime monitoring
1. excalidraw/DDTV/owncast/jitsi-meet/...
All the services assumes a reverse proxy to be setup in the front, they are all listening on
localhost, and a caddy service is listening on the local network interface and proxy the requests to
the services.
TODO: create a private PKI for caddy, to achieve end-to-end encryption between caddy and the
services.
-34
View File
@@ -1,34 +0,0 @@
{
disko,
nixos-rk3588,
mylib,
...
}:
#############################################################
#
# Suzu - Orange Pi 5 Plus, RK3588 + 16GB RAM
#
# https://github.com/astro/microvm.nix
#
#############################################################
let
hostName = "suzu"; # Define your hostname.
in {
imports =
(mylib.scanPaths ./.)
++ [
# import the rk3588 module, which contains the configuration for bootloader/kernel/firmware
nixos-rk3588.nixosModules.orangepi5plus.core
disko.nixosModules.default
];
networking = {inherit hostName;};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "24.05"; # Did you read the comment?
}
-111
View File
@@ -1,111 +0,0 @@
{
# required by impermanence
fileSystems."/persistent".neededForBoot = true;
disko.devices = {
nodev."/" = {
fsType = "tmpfs";
mountOptions = [
"size=2G"
"defaults"
# set mode to 755, otherwise systemd will set it to 777, which cause problems.
# relatime: Update inode access times relative to modify or change time.
"mode=755"
];
};
# TODO: rename to main
disk.sda = {
type = "disk";
# When using disko-install, we will overwrite this value from the commandline
device = "/dev/nvme0n1"; # The device to partition
content = {
type = "gpt";
partitions = {
# The EFI & Boot partition
ESP = {
size = "630M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [
"defaults"
];
};
};
# The root partition
luks = {
size = "100%";
content = {
type = "luks";
name = "encrypted";
settings = {
keyFile = "/dev/disk/by-label/OPI5_DSC"; # The keyfile is stored on a USB stick
# The maximum size of the keyfile is 8192 KiB
# type `cryptsetup --help` to see the compiled-in key and passphrase maximum sizes
keyFileSize = 512 * 64; # match the `bs * count` of the `dd` command
keyFileOffset = 512 * 128; # match the `bs * skip` of the `dd` command
fallbackToPassword = true;
allowDiscards = true;
};
# Whether to add a boot.initrd.luks.devices entry for the specified disk.
initrdUnlock = true;
# encrypt the root partition with luks2 and argon2id, will prompt for a passphrase, which will be used to unlock the partition.
# cryptsetup luksFormat
extraFormatArgs = [
"--type luks2"
"--cipher aes-xts-plain64"
"--hash sha512"
"--iter-time 5000"
"--key-size 256"
"--pbkdf argon2id"
# use true random data from /dev/random, will block until enough entropy is available
"--use-random"
];
extraOpenArgs = [
"--timeout 10"
];
content = {
type = "btrfs";
extraArgs = ["-f"]; # Force override existing partition
subvolumes = {
# mount the top-level subvolume at /btr_pool
# it will be used by btrbk to create snapshots
"/" = {
mountpoint = "/btr_pool";
# btrfs's top-level subvolume, internally has an id 5
# we can access all other subvolumes from this subvolume.
mountOptions = ["subvolid=5"];
};
"@nix" = {
mountpoint = "/nix";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@persistent" = {
mountpoint = "/persistent";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@tmp" = {
mountpoint = "/tmp";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@snapshots" = {
mountpoint = "/snapshots";
mountOptions = ["compress-force=zstd:1" "noatime"];
};
"@swap" = {
mountpoint = "/swap";
swap.swapfile.size = "8192M";
};
};
};
};
};
};
};
};
};
}
@@ -1,39 +0,0 @@
{
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.loader = {
# depending on how you configured your disk mounts, change this to /boot or /boot/efi.
efi.efiSysMountPoint = "/boot/";
efi.canTouchEfiVariables = true;
# do not use systemd-boot here, it has problems when running `nixos-install`
grub = {
device = "nodev";
efiSupport = true;
};
};
# clear /tmp on boot to get a stateless /tmp directory.
boot.tmp.cleanOnBoot = true;
boot.initrd.availableKernelModules = ["nvme" "usbhid" "usb_storage"];
boot.initrd.kernelModules = [];
boot.kernelModules = [];
boot.extraModulePackages = [];
# 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.useDHCP = lib.mkDefault true;
# networking.interfaces.enP3p49s0.useDHCP = lib.mkDefault true;
# networking.interfaces.enP4p65s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
}
-75
View File
@@ -1,75 +0,0 @@
{
impermanence,
pkgs,
...
}: {
imports = [
impermanence.nixosModules.impermanence
];
environment.systemPackages = [
# `sudo ncdu -x /`
pkgs.ncdu
];
# There are two ways to clear the root filesystem on every boot:
## 1. use tmpfs for /
## 2. (btrfs/zfs only)take a blank snapshot of the root filesystem and revert to it on every boot via:
## boot.initrd.postDeviceCommands = ''
## mkdir -p /run/mymount
## mount -o subvol=/ /dev/disk/by-uuid/UUID /run/mymount
## btrfs subvolume delete /run/mymount
## btrfs subvolume snapshot / /run/mymount
## '';
#
# See also https://grahamc.com/blog/erase-your-darlings/
# NOTE: impermanence only mounts the directory/file list below to /persistent
# If the directory/file already exists in the root filesystem, you should
# move those files/directories to /persistent first!
environment.persistence."/persistent" = {
# sets the mount option x-gvfs-hide on all the bind mounts
# to hide them from the file manager
hideMounts = true;
directories = [
"/etc/NetworkManager/system-connections"
"/etc/ssh"
"/etc/nix/inputs"
"/etc/secureboot" # lanzaboote - secure boot
# my secrets
"/etc/agenix/"
"/var/log"
"/var/lib"
];
files = [
"/etc/machine-id"
];
# the following directories will be passed to /persistent/home/$USER
users.ryan = {
directories = [
"codes"
"nix-config"
"tmp"
{
directory = ".ssh";
mode = "0700";
}
# neovim / remmina / flatpak / ...
".local/share"
".local/state"
# language package managers
".npm"
".conda" # generated by `conda-shell`
"go"
];
files = [
".config/nushell/history.txt"
];
};
};
}
-45
View File
@@ -1,45 +0,0 @@
# microvm.nix
## Commands
> https://github.com/astro/microvm.nix/blob/main/doc/src/microvm-command.md
```bash
# list vm
microvm -l
# update vm
microvm -u my-microvm
# show logs of a vm
journalctl -u microvm@my-microvm -n 50
# stop vm
systemctl stop microvm@$NAME
# remove vm
rm -rf /var/lib/microvms/$NAME
# Run a MicroVM in foreground(for testing)
# You have to stop the vm before running this command!
microvm -r my-microvm
# Stop a MicroVM that is running in foreground
## 1. run `sudo shutdown -h now` in the vm
## 2. run `systemctl stop microvm@my-microvm` in the host
```
## VM's pros compared to container
1. VM has its own kernel, so it can use a fullfeatured kernel or customise the kernel's
configuration, without affecting the host.
1. VM use a fullfeatured init system, so it can run services like a real machine.
1. VM can use a fullfeatured network stack, so it can run network services like a real machine. it's
very useful for hosting some network services(such as tailscale, dae, etc).
## FAQ
### 1. enter the vm without ssh
[Enter running machine as systemd service](https://github.com/astro/microvm.nix/issues/123)
-35
View File
@@ -1,35 +0,0 @@
{
myvars,
mylib,
daeuniverse,
agenix,
microvm,
mysecrets,
nuenv,
...
}: {
imports = [
# Include the microvm host module
microvm.nixosModules.host
];
microvm.vms = {
suzi = {
autostart = true;
restartIfChanged = true;
specialArgs = {inherit myvars mylib daeuniverse agenix mysecrets nuenv;};
config.imports = [./suzi];
};
mitsuha = {
autostart = true;
restartIfChanged = true;
specialArgs = {inherit myvars mylib nuenv;};
config.imports = [./mitsuha];
};
};
}
@@ -1,67 +0,0 @@
{mylib, ...}: {
imports =
(mylib.scanPaths ./.)
++ [
../../../../modules/nixos/base/ssh.nix
../../../../modules/nixos/base/user-group.nix
../../../../modules/base.nix
];
microvm = {
mem = 1024; # RAM allocation in MB
vcpu = 1; # Number of Virtual CPU cores
interfaces = [
{
type = "tap";
id = "vm-mitsuha"; # should be prefixed with "vm-"
mac = "02:00:00:00:00:02"; # Unique MAC address
}
];
# Block device images for persistent storage
# microvm use tmpfs for root(/), so everything else
# is ephemeral and will be lost on reboot.
#
# you can check this by running `df -Th` & `lsblk` in the VM.
volumes = [
{
mountPoint = "/var";
image = "var.img";
size = 512;
}
{
mountPoint = "/etc";
image = "etc.img";
size = 50;
}
];
# shares can not be set to `neededForBoot = true;`
# so if you try to use a share in boot script(such as system.activationScripts), it will fail!
shares = [
{
# It is highly recommended to share the host's nix-store
# with the VMs to prevent building huge images.
# a host's /nix/store will be picked up so that no
# squashfs/erofs will be built for it.
#
# by this way, /nix/store is readonly in the VM,
# and thus the VM can't run any command that modifies
# the store. such as nix build, nix shell, etc...
# if you want to run nix commands in the VM, see
# https://github.com/astro/microvm.nix/blob/main/doc/src/shares.md#writable-nixstore-overlay
tag = "ro-store"; # Unique virtiofs daemon tag
proto = "virtiofs"; # virtiofs is faster than 9p
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
];
hypervisor = "qemu";
# Control socket for the Hypervisor so that a MicroVM can be shutdown cleanly
socket = "control.socket";
};
system.stateVersion = "24.05";
}
@@ -1,19 +0,0 @@
{myvars, ...}: let
hostName = "mitsuha";
inherit (myvars.networking) mainGateway nameservers;
inherit (myvars.networking.hostsAddr.${hostName}) ipv4;
ipv4WithMask = "${ipv4}/24";
in {
systemd.network.enable = true;
systemd.network.networks."20-lan" = {
matchConfig.Type = "ether";
networkConfig = {
Address = [ipv4WithMask];
Gateway = mainGateway;
DNS = nameservers;
DHCP = "no";
};
};
}
@@ -1,42 +0,0 @@
{pkgs, ...}:
# =============================================================
#
# Tailscale - your own private network(VPN) that uses WireGuard
#
# It's open source and free for personal use,
# and it's really easy to setup and use.
# Tailscale has great client coverage for Linux, windows, Mac, android, and iOS.
# Tailscale is more mature and stable compared to other alternatives such as netbird/netmaker.
# Maybe I'll give netbird/netmaker a try when they are more mature, but for now, I'm sticking with Tailscale.
#
# How to use:
# 1. Create a Tailscale account at https://login.tailscale.com
# 2. Login via `tailscale login`
# 3. join into your Tailscale network via `tailscale up --advertise-routes 192.168.5.0/24`
# 4. If you prefer automatic connection to Tailscale, use the `authKeyFile` option` in the config below.
#
# Status Data:
# `journalctl -u tailscaled` shows tailscaled's logs
# logs indicate that tailscale store its data in /var/lib/tailscale
# which is already persistent across reboots(via impermanence.nix)
#
# References:
# https://github.com/NixOS/nixpkgs/blob/nixos-24.05/nixos/modules/services/networking/tailscale.nix
#
# =============================================================
{
# make the tailscale command usable to users
environment.systemPackages = [pkgs.tailscale];
# enable the tailscale service
services.tailscale = {
enable = true;
port = 41641;
interfaceName = "tailscale0";
# allow the Tailscale UDP port through the firewall
openFirewall = true;
useRoutingFeatures = "server";
extraUpFlags = "--advertise-routes 192.168.5.0/24";
# authKeyFile = "/var/lib/tailscale/authkey";
};
}
@@ -1,48 +0,0 @@
# Dae - NixOS Router
A router(IPv4 only) with a transparent proxy to bypass the G|F|W.
NOTE: dae do not provides a http/socks5 proxy server, so a v2ray server is running on
[idols_kana](../idols_kana/proxy.nix) to provides a http/socks5 proxy service.
## Troubleshooting
### Can not access the global internet
1. Check whether the subscription url is accessible.
- If not, then you need to get a new subscription url and update the `dae`'s configuration.
1. Check the `dae` service's log by `journalctl -u dae -n 1000`.
### DNS cannot be resolved
1. `sudo systemctl stop dae`, then try to resolve the domain name again.
- If it works, the problem is caused by `dae` service.
- check dae's log by `journalctl -u dae -n 1000`
1. DNS & DHCP is provided by `dnsmasq` service, check the configuration of `dnsmasq`.
### DHCP cannot be obtained
1. `ss -tunlp`, check if `dnsmasq` is running and listening on udp port 67.
1. `journalctl -u dnsmasq -n 1000` to check the log of `dnsmasq`.
1. Request a new IP address by disconnect and reconnect one of your devices' wifi.
1. `nix shell nixpkgs#dhcpdump` and then `sudo dhcpdump -i br-lan`, check if the DHCP request is
received by `dnsmasq`.
1. The server listens on UDP port number 67, and the client listens on UDP port number 68.
1. DHCP operations fall into four phases:
1. Server **discovery**: The DHCP client broadcasts a DHCPDISCOVER message on the network
subnet using the destination address 255.255.255.255 (limited broadcast) or the specific
subnet broadcast address (directed broadcast).
1. IP lease **offer**: When a DHCP server receives a DHCPDISCOVER message from a client, which
is an IP address lease request, the DHCP server reserves an IP address for the client and
makes a lease offer by sending a DHCPOFFER message to the client.
1. IP lease **request**: In response to the DHCP offer, the client replies with a DHCPREQUEST
message, broadcast to the server,[a] requesting the offered address.
1. IP lease **acknowledgement**: When the DHCP server receives the DHCPREQUEST message from
the client, it sends a DHCPACK packet to the client, which includes the lease duration and
any other configuration information that the client might have requested.
1. So if you see only `DISCOVER` messages, the dhsmasq is not working properly.
## References
- <https://github.com/ghostbuster91/blogposts/blob/main/router2023-part2/main.md>
- <https://github.com/ghostbuster91/nixos-router>
@@ -1,326 +0,0 @@
# https://github.com/daeuniverse/dae/discussions/81
# https://github.com/daeuniverse/dae/blob/main/example.dae
# load all dae files placed in ./config.d/
include {
config.d/*.dae
}
global {
##### Software options.
# tproxy port to listen on. It is NOT a HTTP/SOCKS port, and is just used by eBPF program.
# In normal case, you do not need to use it.
tproxy_port: 12345
# Set it true to protect tproxy port from unsolicited traffic. Set it false to allow users to use self-managed
# iptables tproxy rules.
tproxy_port_protect: true
# If not zero, traffic sent from dae will be set SO_MARK. It is useful to avoid traffic loop with iptables tproxy
# rules.
so_mark_from_dae: 1
# Log level: error, warn, info, debug, trace.
log_level: info
# Disable waiting for network before pulling subscriptions.
disable_waiting_network: false
##### Interface and kernel options.
# The LAN interface to bind. Use it if you want to proxy LAN.
# Multiple interfaces split by ",".
lan_interface: br-lan
# The WAN interface to bind. Use it if you want to proxy localhost.
# Multiple interfaces split by ",". Use "auto" to auto detect.
#
# Disable this to avoid problems with the proxy server that prevent the subscription link from being updated
# wan_interface: auto
# Automatically configure Linux kernel parameters like ip_forward and send_redirects. Check out
# https://github.com/daeuniverse/dae/blob/main/docs/en/user-guide/kernel-parameters.md to see what will dae do.
auto_config_kernel_parameter: false
##### Node connectivity check.
# Host of URL should have both IPv4 and IPv6 if you have double stack in local.
# First is URL, others are IP addresses if given.
# Considering traffic consumption, it is recommended to choose a site with anycast IP and less response.
#tcp_check_url: 'http://cp.cloudflare.com'
tcp_check_url: 'http://cp.cloudflare.com,1.1.1.1,2606:4700:4700::1111'
# The HTTP request method to `tcp_check_url`. Use 'HEAD' by default because some server implementations bypass
# accounting for this kind of traffic.
tcp_check_http_method: HEAD
# This DNS will be used to check UDP connectivity of nodes. And if dns_upstream below contains tcp, it also be used to check
# TCP DNS connectivity of nodes.
# First is URL, others are IP addresses if given.
# This DNS should have both IPv4 and IPv6 if you have double stack in local.
#udp_check_dns: 'dns.google.com:53'
udp_check_dns: 'dns.google.com:53,8.8.8.8,2001:4860:4860::8888'
check_interval: 30s
# Group will switch node only when new_latency <= old_latency - tolerance.
check_tolerance: 50ms
##### Connecting options.
# Optional values of dial_mode are:
# 1. "ip". Dial proxy using the IP from DNS directly. This allows your ipv4, ipv6 to choose the optimal path
# respectively, and makes the IP version requested by the application meet expectations. For example, if you
# use curl -4 ip.sb, you will request IPv4 via proxy and get a IPv4 echo. And curl -6 ip.sb will request IPv6.
# This may solve some weird full-cone problem if your are be your node support that. Sniffing will be disabled
# in this mode.
# 2. "domain". Dial proxy using the domain from sniffing. This will relieve DNS pollution problem to a great extent
# if have impure DNS environment. Generally, this mode brings faster proxy response time because proxy will
# re-resolve the domain in remote, thus get better IP result to connect. This policy does not impact routing.
# That is to say, domain rewrite will be after traffic split of routing and dae will not re-route it.
# 3. "domain+". Based on domain mode but do not check the reality of sniffed domain. It is useful for users whose
# DNS requests do not go through dae but want faster proxy response time. Notice that, if DNS requests do not
# go through dae, dae cannot split traffic by domain.
# 4. "domain++". Based on domain+ mode but force to re-route traffic using sniffed domain to partially recover
# domain based traffic split ability. It doesn't work for direct traffic and consumes more CPU resources.
dial_mode: domain
# Allow insecure TLS certificates. It is not recommended to turn it on unless you have to.
allow_insecure: false
# Timeout to waiting for first data sending for sniffing. It is always 0 if dial_mode is ip. Set it higher is useful
# in high latency LAN network.
sniffing_timeout: 100ms
# TLS implementation. tls is to use Go's crypto/tls. utls is to use uTLS, which can imitate browser's Client Hello.
tls_implementation: tls
# The Client Hello ID for uTLS to imitate. This takes effect only if tls_implementation is utls.
# See more: https://github.com/daeuniverse/dae/blob/331fa23c16/component/outbound/transport/tls/utls.go#L17
utls_imitate: chrome_auto
}
# See https://github.com/daeuniverse/dae/blob/main/docs/en/configuration/dns.md for full examples.
dns {
# For example, if ipversion_prefer is 4 and the domain name has both type A and type AAAA records, the dae will only
# respond to type A queries and response empty answer to type AAAA queries.
ipversion_prefer: 4
# Give a fixed ttl for domains. Zero means that dae will request to upstream every time and not cache DNS results
# for these domains.
#fixed_domain_ttl {
# ddns.example.org: 10
# test.example.org: 3600
#}
upstream {
# Value can be scheme://host:port, where the scheme can be tcp/udp/tcp+udp.
# If host is a domain and has both IPv4 and IPv6 record, dae will automatically choose
# IPv4 or IPv6 to use according to group policy (such as min latency policy).
# Please make sure DNS traffic will go through and be forwarded by dae, which is REQUIRED for domain routing.
# If dial_mode is "ip", the upstream DNS answer SHOULD NOT be polluted, so domestic public DNS is not recommended.
alidns: 'udp://223.5.5.5:53'
googledns: 'tcp+udp://8.8.8.8:53'
}
routing {
# According to the request of dns query, decide to use which DNS upstream.
# Match rules from top to bottom.
request {
# Lookup China mainland domains using alidns, otherwise googledns.
qname(geosite:cn) -> alidns
# fallback is also called default.
fallback: googledns
# other custom rules
qname(full:analytics.google.com) -> googledns # do not block google analytics(console)
qname(regex: '.+\.nixos.org$') -> googledns
qname(geosite:category-ads) -> reject
qname(geosite:category-ads-all) -> reject
qtype(aaaa) -> reject
qname(regex: '.+\.linkedin$') -> googledns
}
# According to the response of dns query, decide to accept or re-lookup using another DNS upstream.
# Match rules from top to bottom.
response {
# Trusted upstream. Always accept its result.
upstream(googledns) -> accept
# Possibly polluted(domain resolved to a private ip), re-lookup using googledns.
ip(geoip:private) && !qname(geosite:cn) -> googledns
fallback: accept
}
}
}
# Node group (outbound).
group {
proxy {
filter: name(keyword: 'Hong Kong')
filter: name(keyword: '香港')
filter: name(keyword: 'Singapore')
filter: name(keyword: '新加坡')
# Filter nodes and give a fixed latency offset to archive latency-based failover.
# In this example, there is bigger possibility to choose US node even if original latency of US node is higher.
filter: name(keyword: 'USA') [add_latency: -500ms]
filter: name(keyword: '美国') [add_latency: -500ms]
filter: name(keyword: 'UK') [add_latency: -300ms]
# filter: name(keyword: '英国') [add_latency: -300ms]
# filter: name(keyword: 'Japan') [add_latency: 300ms]
# filter: name(keyword: '日本') [add_latency: 300ms]
# Other filters:
# Filter nodes from the global node pool defined by the subscription and node section above.
# filter: subtag(regex: '^my_', another_sub) && !name(keyword: 'ExpireAt:')
# Filter nodes from the global node pool defined by tag.
# filter: name('node_a','node_b')
# Select the node with min average of the last 10 latencies from the group for every connection.
policy: min_avg10
# Other policies:
# random - Randomly select a node from the group for every connection.
# fixed(0) - Select the first node from the group for every connection.
# min - Select the node with min last latency from the group for every connection.
# min_moving_avg - Select the node with min moving average of latencies from the group for every connection.
}
media {
filter: name(keyword: 'Hong Kong')
filter: name(keyword: '香港')
filter: name(keyword: 'Singapore')
filter: name(keyword: '新加坡')
filter: name(keyword: 'USA') [add_latency: -500ms]
filter: name(keyword: '美国') [add_latency: -500ms]
filter: name(keyword: 'UK') [add_latency: -300ms]
filter: name(keyword: '英国') [add_latency: -300ms]
filter: name(keyword: 'Japan') [add_latency: 300ms]
filter: name(keyword: '日本') [add_latency: 300ms]
policy: min_avg10
}
ssh-proxy {
filter: name(keyword: 'UK')
filter: name(keyword: '英国')
policy: min_avg10
}
sg {
filter: name(keyword: 'Singapore')
filter: name(keyword: '新加坡')
policy: min_avg10
}
usa {
filter: name(keyword: 'USA')
filter: name(keyword: '美国')
policy: min_avg10
}
}
# See https://github.com/daeuniverse/dae/blob/main/docs/en/configuration/routing.md for full examples.
# Pname has the highest priority, so should be placed in the front.
# Priority of other rules is the same as the order of the rules defined in this file.
routing {
### Preset rules.
# Network managers in localhost should be direct to
# avoid false negative network connectivity check when binding to WAN.
pname(NetworkManager) -> direct
pname(systemd-networkd) -> direct
# Put it in the front to prevent broadcast, multicast and other packets that should be sent to the LAN from being
# forwarded by the proxy.
# "dip" means destination IP.
dip(224.0.0.0/3, 'ff00::/8') -> direct
# This line allows you to access private addresses directly instead of via your proxy. If you really want to access
# private addresses in your proxy host network, modify the below line.
dip(geoip:private) -> direct
# --- Core rules ---#
# Disable HTTP3(QUIC) because it usually consumes too much cpu/mem resources.
l4proto(udp) && dport(443) -> block
# Direct access to all Chinese mainland-related IP addresses
dip(geoip:cn) -> direct
domain(geosite:cn) -> direct
# Block ads
domain(full:analytics.google.com) -> proxy # do not block google analytics(console)
domain(geosite:category-ads) -> block
domain(geosite:category-ads-all) -> block
# DNS
dip(8.8.8.8, 8.8.4.4) -> proxy
dip(223.5.5.5, 223.6.6.6) -> direct
domain(full:dns.alidns.com) -> direct
domain(full:dns.googledns.com) -> proxy
domain(full:dns.opendns.com) -> proxy
# --- Rules for other commonly used sites ---#
# SSH - tcp port 22 is blocked by many proxy servers.
dport(22) && !dip(geoip:cn) && !domain(geosite:cn) -> ssh-proxy
### OpenAI
domain(geosite:openai) -> sg
domain(regex:'.+\.openai$') -> sg
# Steam
domain(suffix: steampowered.com) -> direct
domain(suffix: steamserver.net) -> direct
domain(geosite:steam@cn) -> direct
domain(geosite:steam) -> proxy
### Media
domain(geosite:netflix) -> media
### Proxy
domain(suffix: linkedin.com) -> proxy
domain(keyword:'linkedin') -> proxy
domain(regex:'.+\.linkedin\.com$') -> proxy
domain(regex:'.+\.quay\.io$') -> proxy
domain(regex:'.+\.notion\.so$') -> proxy
domain(regex:'.+\.amazon\.com$') -> proxy
domain(regex:'.+\.oracle\.com$') -> proxy
domain(regex:'.+\.docker\.com$') -> proxy
domain(regex:'.+\.kubernetes\.io$') -> proxy
domain(regex:'.+\.nixos\.org$') -> proxy
domain(geosite:microsoft) -> proxy
domain(geosite:linkedin) -> proxy
domain(geosite:twitter) -> proxy
domain(geosite:telegram) -> proxy
domain(geosite:google) -> proxy
domain(geosite:apple) -> proxy
domain(geosite:category-container) -> proxy
domain(geosite:category-dev) -> proxy
domain(geosite:google-scholar) -> proxy
domain(geosite:category-scholar-!cn) -> proxy
### Direct
domain(regex:'.+\.edu\.cn$') -> direct
domain(keyword:'baidu') -> direct
domain(keyword:'bilibili') -> direct
domain(keyword:'taobao') -> direct
domain(keyword:'alibabadns') -> direct
domain(keyword:'alicdn') -> direct
domain(keyword:'tbcache') -> direct
domain(keyword:'zhihu') -> direct
domain(keyword:'douyu') -> direct
domain(geosite:cloudflare-cn) -> direct
# --- Fallback rules ---#
# Access all other foreign sites
domain(geosite:geolocation-!cn) -> proxy
!dip(geoip:cn) -> proxy
fallback: direct
}
@@ -1,61 +0,0 @@
{
config,
pkgs,
daeuniverse,
...
}:
# https://github.com/daeuniverse/flake.nix
let
daeConfigPath = "/etc/dae/config.dae";
subscriptionConfigPath = "/etc/dae/config.d/subscription.dae";
in {
imports = [
daeuniverse.nixosModules.dae
];
# dae - eBPF-based Linux high-performance transparent proxy.
services.dae = {
enable = true;
package = daeuniverse.packages.${pkgs.system}.dae;
disableTxChecksumIpGeneric = false;
configFile = daeConfigPath;
assets = with pkgs; [v2ray-geoip v2ray-domain-list-community];
# alternatively, specify assets dir
# assetsPath = "/etc/dae";
openFirewall = {
enable = true;
port = 12345;
};
};
systemd.services.dae.serviceConfig = {
Restart = "on-failure";
RestartSec = 10;
};
# dae supports two types of subscriptions: base64 encoded proxies, and sip008.
# subscription can be a url return the subscription, or a file path that contains the subscription.
#
# Nix decrypt and merge my dae's base config and subscription config here.
# the subscription config is something like:
# ```
# subscription {
# 'https://www.example.com/subscription/link'
# 'https://example.com/no_tag_link'
# }
# node {
# # Support socks5, http, https, ss, ssr, vmess, vless, trojan, trojan-go, tuic, juicity
# node_a: 'trojan://'
# node_b: 'trojan://'
# node_c: 'vless://'
# node_d: 'vless://'
# node_e: 'vmess://'
# node_f: 'tuic://'
# node_h: 'juicity://'
# }
# ```
system.activationScripts.installDaeConfig = ''
install -Dm 600 ${./config.dae} ${daeConfigPath}
install -Dm 600 ${config.age.secrets."dae-subscription.dae".path} ${subscriptionConfigPath}
'';
}
@@ -1,70 +0,0 @@
{mylib, ...}: {
imports =
(mylib.scanPaths ./.)
++ [
../../../../secrets/nixos.nix
../../../../modules/nixos/base/ssh.nix
../../../../modules/nixos/base/user-group.nix
../../../../modules/base.nix
];
modules.secrets.server.network.enable = true;
microvm = {
mem = 1024; # RAM allocation in MB
vcpu = 1; # Number of Virtual CPU cores
interfaces = [
{
type = "tap";
id = "vm-suzi"; # should be prefixed with "vm-"
mac = "02:00:00:00:00:01"; # unique MAC address
}
];
# Block device images for persistent storage
# microvm use tmpfs for root(/), so everything else
# is ephemeral and will be lost on reboot.
#
# you can check this by running `df -Th` & `lsblk` in the VM.
volumes = [
{
mountPoint = "/var";
image = "var.img";
size = 512;
}
{
mountPoint = "/etc";
image = "etc.img";
size = 50;
}
];
# shares can not be set to `neededForBoot = true;`
# so if you try to use a share in boot script(such as system.activationScripts), it will fail!
shares = [
{
# It is highly recommended to share the host's nix-store
# with the VMs to prevent building huge images.
# a host's /nix/store will be picked up so that no
# squashfs/erofs will be built for it.
#
# by this way, /nix/store is readonly in the VM,
# and thus the VM can't run any command that modifies
# the store. such as nix build, nix shell, etc...
# if you want to run nix commands in the VM, see
# https://github.com/astro/microvm.nix/blob/main/doc/src/shares.md#writable-nixstore-overlay
tag = "ro-store"; # Unique virtiofs daemon tag
proto = "virtiofs"; # virtiofs is faster than 9p
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
];
hypervisor = "qemu";
# Control socket for the Hypervisor so that a MicroVM can be shutdown cleanly
socket = "control.socket";
};
system.stateVersion = "24.05";
}
@@ -1,180 +0,0 @@
{
lib,
myvars,
...
}: let
hostName = "suzi";
inherit (myvars.networking) mainGateway nameservers;
inherit (myvars.networking.hostsAddr.${hostName}) ipv4;
ipv4WithMask = "${ipv4}/24";
dhcpRange = {
start = "192.168.5.5";
end = "192.168.5.99";
};
in {
boot.kernel.sysctl = {
# https://github.com/ghostbuster91/blogposts/blob/main/router2023-part2/main.md
# https://github.com/daeuniverse/dae/blob/main/docs/en/user-guide/kernel-parameters.md
# forward network packets that are not destined for the interface on which they were received
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
"net.ipv4.conf.br-lan.rp_filter" = 1;
"net.ipv4.conf.br-lan.send_redirects" = 0;
};
# Docker uses iptables internally to setup NAT for containers.
# This module disables the ip_tables kernel module, which is required for nftables to work.
# So make sure to disable docker here.
virtualisation.docker.enable = lib.mkForce false;
networking = {
useNetworkd = true;
useDHCP = false;
networkmanager.enable = false;
wireless.enable = false; # Enables wireless support via wpa_supplicant.
# No local firewall.
nat.enable = false;
firewall.enable = false;
# https://github.com/NixOS/nixpkgs/blob/nixos-24.05/nixos/modules/services/networking/nftables.nix
nftables = {
enable = true;
# Check the applied rules with `nft -a list ruleset`.
# Since this is a internal bypass router, we don't need to do NAT & can forward all traffic.
ruleset = ''
# Check out https://wiki.nftables.org/ for better documentation.
# Table for both IPv4 and IPv6.
table inet filter {
chain input {
type filter hook input priority 0;
# accept any localhost traffic
iifname lo accept
# accept any lan traffic
iifname br-lan accept
# count and drop any other traffic
counter drop
}
# Allow all outgoing connections.
chain output {
type filter hook output priority 0;
accept
}
# Allow all forwarding all traffic.
chain forward {
type filter hook forward priority 0;
accept
}
}
'';
};
};
# https://nixos.wiki/wiki/Systemd-networkd
systemd.network = {
netdevs = {
# Create the bridge interface
"20-br-lan" = {
netdevConfig = {
Kind = "bridge";
Name = "br-lan";
};
};
};
# This is a bypass router, so we do not need a wan interface here.
networks = {
"30-lan0" = {
# match the interface by type
matchConfig.Type = "ether";
# Connect to the bridge
networkConfig = {
Bridge = "br-lan";
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "enslaved";
};
# Configure the bridge device we just created
"40-br-lan" = {
matchConfig.Name = "br-lan";
address = [
# configure addresses including subnet mask
ipv4WithMask # forwards all traffic to the gateway except for the router address itself
];
routes = [
# forward all traffic to the main gateway
{routeConfig.Gateway = mainGateway;}
];
bridgeConfig = {};
linkConfig.RequiredForOnline = "routable";
};
};
};
# resolved is conflict with dnsmasq
services.resolved.enable = false;
services.dnsmasq = {
enable = true;
# resolve local queries (add 127.0.0.1 to /etc/resolv.conf)
resolveLocalQueries = true; # may be conflict with dae, disable this.
alwaysKeepRunning = true;
# https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=tree
settings = {
# upstream DNS servers
server = nameservers;
# forces dnsmasq to try each query with each server strictly
# in the order they appear in the config.
strict-order = true;
# Never forward plain names (without a dot or domain part)
domain-needed = true;
# Never forward addresses in the non-routed address spaces(e.g. private IP).
bogus-priv = true;
# don't needlessly read /etc/resolv.conf which only contains the localhost addresses of dnsmasq itself.
no-resolv = true;
# Cache dns queries.
cache-size = 1000;
dhcp-range = ["${dhcpRange.start},${dhcpRange.end},24h"];
interface = "br-lan";
dhcp-sequential-ip = true;
dhcp-option = [
# Override the default route supplied by dnsmasq, which assumes the
# router is the same machine as the one running dnsmasq.
"option:router,${ipv4}"
"option:dns-server,${ipv4}"
];
# local domains
local = "/lan/";
domain = "lan";
expand-hosts = true;
# don't use /etc/hosts
no-hosts = true;
address = [
# "/surfer.lan/192.168.10.1"
];
};
};
# monitoring with prometheus
# https://github.com/NixOS/nixpkgs/blob/nixos-24.05/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
services.prometheus.exporters.dnsmasq = {
enable = true;
listenAddress = "0.0.0.0";
port = 9153;
openFirewall = false;
leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
};
# The service irqbalance is useful as it assigns certain IRQ calls to specific CPUs instead of
# letting the first CPU core to handle everything.
# This is supposed to increase performance by hitting CPU cache more often.
services.irqbalance.enable = false;
}
-45
View File
@@ -1,45 +0,0 @@
{myvars, ...}: let
hostName = "suzu";
inherit (myvars.networking) mainGateway nameservers;
inherit (myvars.networking.hostsAddr.${hostName}) iface ipv4;
ipv4WithMask = "${ipv4}/24";
in {
boot.kernel.sysctl = {
# forward network packets that are not destined for the interface on which they were received
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
};
networking.useNetworkd = true;
systemd.network.enable = true;
# A bridge to link all VM's TAP interfaces into local network.
# https://github.com/astro/microvm.nix/blob/main/doc/src/simple-network.md
systemd.network.networks."10-lan" = {
# match on the main interface and all VM interfaces
matchConfig.Name = [iface "vm-*"];
networkConfig = {
Bridge = "br0";
};
};
systemd.network.netdevs."br0" = {
netdevConfig = {
Name = "br0";
Kind = "bridge";
};
};
# Add ipv4 address to the bridge.
systemd.network.networks."10-lan-bridge" = {
matchConfig.Name = "br0";
networkConfig = {
Address = [ipv4WithMask];
Gateway = mainGateway;
DNS = nameservers;
IPv6AcceptRA = true;
};
linkConfig.RequiredForOnline = "routable";
};
}
+2 -10
View File
@@ -2,13 +2,6 @@
1. `12kingdoms`:
1. `shoukei`: NixOS on Macbook Pro 2020 Intel i5, 13.3-inch, 16G RAM + 512G SSD.
1. `suzu`: Orange Pi 5, RK3588s(4xA76 + 4xA55), GPU(4Cores, Mail-G610), NPU(6Tops@int8), 8G RAM +
256G SSD.
- Network related services running via microvm.nix, such as router(transparent proxy - dae),
tailscale subrouter, etc.
1. `rakushun`: Orange Pi 5 Plus, RK3588(4xA76 + 4xA55), GPU(4Cores, Mail-G610), NPU(6Tops@int8),
16G RAM + 2T SSD.
- Not used now.
1. `darwin`(macOS)
1. `fern`: MacBook Pro 2022 13-inch M2 16G, mainly for business.
1. `harmonica`: MacBook Pro 2020 13-inch i5 16G, for personal use.
@@ -20,9 +13,8 @@
and other services.
3. `ruby`: Not used now.
4. `kana`: Not used now.
1. `rolling_girls`: My RISCV64 hosts.
1. `nozomi`: Lichee Pi 4A, TH1520(4xC910@2.0G), 16GB RAM + 32G eMMC + 128G SD Card.
2. `yukina`: Milk-V Mars, JH7110(4xU74@1.5 GHz), 4G RAM + No eMMC + 64G SD Card.
1. Other aarch64/riscv64 SBCs:
[ryan4yin/nixos-config-sbc](https://github.com/ryan4yin/nixos-config-sbc)
## How to add a new host
+80 -30
View File
@@ -1,42 +1,96 @@
# auto disk partitioning:
# nix run github:nix-community/disko -- --mode disko ./disko-fs.nix
{
let
cryptKeyFile = "/etc/agenix/hdd-luks-crypt-key";
unlockDisk = "data-encrypted";
in {
fileSystems."/data/fileshare/public".depends = ["/data/fileshare"];
# By adding this crypttab entry, the disk will be unlocked by systemd-cryptsetup@xxx.service at boot time.
# This systemd service is running after agenix, so that the keyfile is already available.
environment.etc = {
"crypttab".text = ''
${unlockDisk} /dev/disk/by-partlabel/disk-${unlockDisk}-luks ${cryptKeyFile} luks,discard,keyfile-size=32768,keyfile-offset=65536
'';
};
disko.devices = {
disk.data-apps = {
disk.data-encrypted = {
type = "disk";
device = "/dev/disk/by-id/ata-WDC_WD40EJRX-89T1XY0_WD-WCC7K0XDCZE6";
device = "/dev/disk/by-id/ata-WDC_WD40EZRZ-22GXCB0_WD-WCC7K7VV9613";
content = {
type = "gpt";
partitions.data-apps = {
size = "100%";
content = {
type = "btrfs";
# extraArgs = ["-f"]; # Override existing partition
subvolumes = {
"@persistent" = {
mountpoint = "/data/apps";
mountOptions = [
"compress-force=zstd:1"
# https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html
"nofail"
];
partitions = {
luks = {
size = "100%";
content = {
type = "luks";
name = "data-encrypted";
settings = {
keyFile = cryptKeyFile;
# The maximum size of the keyfile is 8192 KiB
# type `cryptsetup --help` to see the compiled-in key and passphrase maximum sizes
# to generate a key file:
# dd bs=512 count=1024 iflag=fullblock if=/dev/random of=./hdd-luks-crypt-key
keyFileSize = 512 * 64; # match the `bs * count` of the `dd` command
keyFileOffset = 512 * 128; # match the `bs * skip` of the `dd` command
fallbackToPassword = true;
allowDiscards = true;
};
"@backups" = {
mountpoint = "/data/backups";
mountOptions = ["compress-force=zstd:1" "noatime" "nofail"];
};
"@snapshots" = {
mountpoint = "/data/apps-snapshots";
mountOptions = ["compress-force=zstd:1" "noatime" "nofail"];
# Whether to add a boot.initrd.luks.devices entry for the specified disk.
# The keyfile do not exist before agenix decrypts its data, do we have to disable this option.
# Otherwise, the initrd will fail to unlock the disk, which causes the boot process to fail.
initrdUnlock = false;
# encrypt the root partition with luks2 and argon2id, will prompt for a passphrase, which will be used to unlock the partition.
# cryptsetup luksFormat
extraFormatArgs = [
"--type luks2"
"--cipher aes-xts-plain64"
"--hash sha512"
"--iter-time 5000"
"--key-size 256"
"--pbkdf argon2id"
# use true random data from /dev/random, will block until enough entropy is available
"--use-random"
];
extraOpenArgs = [
"--timeout 10"
];
content = {
type = "btrfs";
extraArgs = ["-f"]; # Force override existing partition
subvolumes = {
"@apps" = {
mountpoint = "/data/apps";
mountOptions = [
"compress-force=zstd:1"
# https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html
"nofail"
];
};
"@fileshare" = {
mountpoint = "/data/fileshare";
mountOptions = ["compress-force=zstd:1" "noatime" "nofail"];
};
"@backups" = {
mountpoint = "/data/backups";
mountOptions = ["compress-force=zstd:1" "noatime" "nofail"];
};
"@snapshots" = {
mountpoint = "/data/apps-snapshots";
mountOptions = ["compress-force=zstd:1" "noatime" "nofail"];
};
};
};
};
};
};
};
};
disk.data-fileshare = {
disk.data-public = {
type = "disk";
device = "/dev/disk/by-id/ata-WDC_WD40EZRZ-22GXCB0_WD-WCC7K7VV9613";
device = "/dev/disk/by-id/ata-WDC_WD40EJRX-89T1XY0_WD-WCC7K0XDCZE6";
content = {
type = "gpt";
partitions.data-fileshare = {
@@ -46,13 +100,9 @@
# extraArgs = ["-f"]; # Override existing partition
subvolumes = {
"@persistent" = {
mountpoint = "/data/fileshare";
mountpoint = "/data/fileshare/public";
mountOptions = ["compress-force=zstd:1" "nofail"];
};
"@snapshots" = {
mountpoint = "/data/fileshare-snapshots";
mountOptions = ["compress-force=zstd:1" "noatime" "nofail"];
};
};
};
};
+1 -1
View File
@@ -3,7 +3,7 @@
myvars,
...
}: let
dataDir = "/data/apps/transmission";
dataDir = "/data/fileshare/public/transmission";
name = "transmission";
in {
# the headless Transmission BitTorrent daemon
+1 -5
View File
@@ -28,11 +28,7 @@ Clusters running as virtual machines on the KubeVirt cluster, for testing and pr
1. `k3s-prod-1-worker-1`
1. `k3s-prod-1-worker-2`
1. `k3s-prod-1-worker-3`
1. For testing:.
1. `k3s-test-1-master-1`
2. `k3s-test-1-worker-1`
3. `k3s-test-1-worker-2`
4. `k3s-test-1-worker-3`
1. For testing:. 2. `k3s-test-1-master-1` 3. `k3s-test-1-master-2` 4. `k3s-test-1-master-3`
## Kubernetes Resources
+2 -2
View File
@@ -58,8 +58,8 @@ in {
#
# pre-allocate hugepages manually(for kubevirt guest vms)
# NOTE: the hugepages allocated here can not be used for other purposes!
# so we should left some memory for the host OS
# so we should left some memory for the host OS and other vms that don't use hugepages
"hugepagesz=1G"
"hugepages=54"
"hugepages=48" # use 75% of the total memory for hugepages
];
}
+2 -2
View File
@@ -52,8 +52,8 @@ in {
#
# pre-allocate hugepages manually(for kubevirt guest vms)
# NOTE: the hugepages allocated here can not be used for other purposes!
# so we should left some memory for the host OS
# so we should left some memory for the host OS and other vms that don't use hugepages
"hugepagesz=1G"
"hugepages=55"
"hugepages=48" # use 75% of the total memory for hugepages
];
}
+2 -2
View File
@@ -52,9 +52,9 @@ in {
#
# pre-allocate hugepages manually(for kubevirt guest vms)
# NOTE: the hugepages allocated here can not be used for other purposes!
# so we should left some memory for the host OS
# so we should left some memory for the host OS and other vms that don't use hugepages
"hugepagesz=1G"
"hugepages=16"
"hugepages=15" # use 15/24 of the total memory for hugepages
# https://kubevirt.io/user-guide/compute/host-devices/
#
-78
View File
@@ -1,78 +0,0 @@
{
nixos-licheepi4a,
myvars,
...
}:
#############################################################
#
# Nozomi - NixOS configuration for Lichee Pi 4A
#
#############################################################
let
hostName = "nozomi"; # Define your hostname.
in {
imports = [
# import the licheepi4a module, which contains the configuration for bootloader/kernel/firmware
(nixos-licheepi4a + "/modules/licheepi4a.nix")
# import the sd-image module, which contains the fileSystems & kernel parameters for booting from sd card.
(nixos-licheepi4a + "/modules/sd-image/sd-image-lp4a.nix")
];
# Set static IP address / gateway / DNS servers.
networking = {
inherit hostName;
inherit (myvars.networking) defaultGateway nameservers;
inherit (myvars.networking.hostsInterface.${hostName}) interfaces;
wireless = {
# https://wiki.archlinux.org/title/wpa_supplicant
enable = true;
# The path to the file containing the WPA passphrase.
# secrets are not supported well on riscv64, I nned to create this file manually.
# Format: "PSK_WEMEET_PRIVATE_WIFI=your_password"
environmentFile = "/etc/wpa_supplicant.env";
# The network definitions to automatically connect to when wpa_supplicant is running.
networks = {
# read WPAPSK from environmentFile
"shadow_light_ryan".psk = "@PSK_WEMEET_PRIVATE_WIFI@";
};
};
# Failed to enable firewall due to the following error:
# firewall-start[2300]: iptables: Failed to initialize nft: Protocol not supported
firewall.enable = false;
# Configure network proxy if necessary
# proxy.default = "http://user:password@proxy:port/";
# proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# LPI4A's first ethernet interface
# interfaces.end0 = {
# useDHCP = false;
# ipv4.addresses = [
# {
# address = "192.168.5.104";
# prefixLength = 24;
# }
# ];
# };
# LPI4A's second ethernet interface
# interfaces.end1 = {
# useDHCP = false;
# ipv4.addresses = [
# {
# address = "192.168.xx.xx";
# prefixLength = 24;
# }
# ];
# };
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.05"; # Did you read the comment?
}
-78
View File
@@ -1,78 +0,0 @@
{
nixos-licheepi4a,
myvars,
...
}:
#############################################################
#
# Yukina - NixOS configuration for Lichee Pi 4A
#
#############################################################
let
hostName = "yukina"; # Define your hostname.
in {
imports = [
# import the licheepi4a module, which contains the configuration for bootloader/kernel/firmware
(nixos-licheepi4a + "/modules/licheepi4a.nix")
# import the sd-image module, which contains the fileSystems & kernel parameters for booting from sd card.
(nixos-licheepi4a + "/modules/sd-image/sd-image-lp4a.nix")
];
# Set static IP address / gateway / DNS servers.
networking = {
inherit hostName;
inherit (myvars.networking) defaultGateway nameservers;
inherit (myvars.networking.hostsInterface.${hostName}) interfaces;
wireless = {
# https://wiki.archlinux.org/title/wpa_supplicant
enable = true;
# The path to the file containing the WPA passphrase.
# secrets are not supported well on riscv64, I nned to create this file manually.
# Format: "PSK_WEMEET_PRIVATE_WIFI=your_password"
environmentFile = "/etc/wpa_supplicant.env";
# The network definitions to automatically connect to when wpa_supplicant is running.
networks = {
# read WPAPSK from environmentFile
"shadow_light_ryan".psk = "@PSK_WEMEET_PRIVATE_WIFI@";
};
};
# Failed to enable firewall due to the following error:
# firewall-start[2300]: iptables: Failed to initialize nft: Protocol not supported
firewall.enable = false;
# Configure network proxy if necessary
# proxy.default = "http://user:password@proxy:port/";
# proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# LPI4A's first ethernet interface
# interfaces.end0 = {
# useDHCP = false;
# ipv4.addresses = [
# {
# address = "192.168.5.104";
# prefixLength = 24;
# }
# ];
# };
# LPI4A's second ethernet interface
# interfaces.end1 = {
# useDHCP = false;
# ipv4.addresses = [
# {
# address = "192.168.xx.xx";
# prefixLength = 24;
# }
# ];
# };
};
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.05"; # Did you read the comment?
}
+1 -5
View File
@@ -74,11 +74,7 @@
# ```
# 2. Never leave the device and never sent over the network.
# 2. Or just use hardware security keys like Yubikey/CanoKey.
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIKlN+Q/GxvwxDX/OAjJHaNFEznEN4Tw4E4TwqQu/eD6 ryan@idols-ai"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPoa9uEI/gR5+klqTQwvCgD6CD5vT5iD9YCNx2xNrH3B ryan@fern"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPwZ9MdotnyhxIJrI4gmVshExHiZOx+FGFhcW7BaYkfR ryan@harmonica"
];
openssh.authorizedKeys.keys = myvars.sshAuthorizedKeys;
};
nix.settings = {
+7 -1
View File
@@ -1,4 +1,4 @@
_: {
{
# Network discovery, mDNS
# With this enabled, you can access your machine at <hostname>.local
# it's more convenient than using the IP address.
@@ -12,4 +12,10 @@ _: {
userServices = true;
};
};
# Use an NTP server located in the mainland of China to synchronize the system time
networking.timeServers = [
"ntp.aliyun.com" # Aliyun NTP Server
"ntp.tencent.com" # Tencent NTP Server
];
}
+1 -1
View File
@@ -22,7 +22,7 @@
users.users."${myvars.username}" = {
# generated by `mkpasswd -m scrypt`
# we have to use initialHashedPassword here when using tmpfs for /
initialHashedPassword = "$7$CU..../....KDvTIXqLTXpmCaoUy2yC9.$145eM358b7Q0sRXgEBvxctd5EAuEEdao57LmZjc05D.";
inherit (myvars) initialHashedPassword;
home = "/home/${myvars.username}";
isNormalUser = true;
extraGroups = [
+1 -1
View File
@@ -16,7 +16,7 @@
../../base.nix
];
boot.loader.timeout = lib.mkForce 3; # wait for 3 seconds to select the boot entry
boot.loader.timeout = lib.mkForce 7; # wait for x seconds to select the boot entry
# Fix: jasper is marked as broken, refusing to evaluate.
environment.enableAllTerminfo = lib.mkForce false;
}
+1 -1
View File
@@ -15,5 +15,5 @@
../../base.nix
];
boot.loader.timeout = lib.mkForce 3; # wait for 3 seconds to select the boot entry
boot.loader.timeout = lib.mkForce 7; # wait for x seconds to select the boot entry
}
+1 -1
View File
@@ -3,5 +3,5 @@
../base
../../base.nix
];
boot.loader.timeout = lib.mkDefault 3; # wait for 3 seconds to select the boot entry
boot.loader.timeout = lib.mkForce 7; # wait for x seconds to select the boot entry
}
-38
View File
@@ -1,38 +0,0 @@
{
lib,
inputs,
...
} @ args: let
inherit (inputs) haumea;
# Contains all the flake outputs of this system architecture.
data = haumea.lib.load {
src = ./src;
inputs = args;
};
# nix file names is redundant, so we remove it.
dataWithoutPaths = builtins.attrValues data;
# Merge all the machine's data into a single attribute set.
outputs = {
nixosConfigurations = lib.attrsets.mergeAttrsList (map (it: it.nixosConfigurations or {}) dataWithoutPaths);
packages = lib.attrsets.mergeAttrsList (map (it: it.packages or {}) dataWithoutPaths);
# colmena contains some meta info, which need to be merged carefully.
colmenaMeta = {
nodeNixpkgs = lib.attrsets.mergeAttrsList (map (it: it.colmenaMeta.nodeNixpkgs or {}) dataWithoutPaths);
nodeSpecialArgs = lib.attrsets.mergeAttrsList (map (it: it.colmenaMeta.nodeSpecialArgs or {}) dataWithoutPaths);
};
# colmena's per-machine data.
colmena = lib.attrsets.mergeAttrsList (map (it: it.colmena or {}) dataWithoutPaths);
};
in
outputs
// {
inherit data; # for debugging purposes
# NixOS's unit tests.
evalTests = haumea.lib.loadEvalTests {
src = ./tests;
inputs = args // {inherit outputs;};
};
}
@@ -1,67 +0,0 @@
{
# NOTE: the args not used in this file CAN NOT be removed!
# because haumea pass argument lazily,
# and these arguments are used in the functions like `mylib.nixosSystem`, `mylib.colmenaSystem`, etc.
inputs,
lib,
mylib,
myvars,
system,
genSpecialArgs,
...
} @ args: let
# 楽俊, Rakushun
name = "rakushun";
tags = [name "aarch"];
ssh-user = "root";
modules = {
nixos-modules =
(map mylib.relativeToRoot [
"secrets/nixos.nix"
"modules/nixos/server/server-aarch64.nix"
# host specific modules
"hosts/12kingdoms-${name}"
])
++ [
];
home-modules =
map mylib.relativeToRoot [
];
};
inherit (inputs) nixos-rk3588;
baseSpecialArgs = genSpecialArgs system;
rk3588Pkgs = import nixos-rk3588.inputs.nixpkgs {inherit system;};
rk3588SpecialArgs = let
# using the same nixpkgs as nixos-rk3588
inherit (nixos-rk3588.inputs) nixpkgs;
# use aarch64-linux's native toolchain
pkgsKernel = import nixpkgs {inherit system;};
in
baseSpecialArgs
// {
inherit nixpkgs;
# Provide rk3588 inputs as special argument
rk3588 = {inherit nixpkgs pkgsKernel;};
};
rk3588SystemArgs =
modules
// args
// {
inherit (nixos-rk3588.inputs) nixpkgs; # or nixpkgs-unstable
specialArgs = rk3588SpecialArgs;
};
in {
nixosConfigurations.${name} = mylib.nixosSystem rk3588SystemArgs;
colmenaMeta = {
nodeSpecialArgs.${name} = rk3588SpecialArgs;
nodeNixpkgs.${name} = rk3588Pkgs;
};
colmena.${name} =
mylib.colmenaSystem
(rk3588SystemArgs // {inherit tags ssh-user;});
}
@@ -1,65 +0,0 @@
{
# NOTE: the args not used in this file CAN NOT be removed!
# because haumea pass argument lazily,
# and these arguments are used in the functions like `mylib.nixosSystem`, `mylib.colmenaSystem`, etc.
inputs,
lib,
mylib,
myvars,
system,
genSpecialArgs,
...
} @ args: let
# 大木 鈴, Ōki Suzu
name = "suzu";
tags = [name "aarch"];
ssh-user = "root";
modules = {
nixos-modules =
(map mylib.relativeToRoot [
"secrets/nixos.nix"
"modules/nixos/server/server-aarch64.nix"
# host specific modules
"hosts/12kingdoms-${name}"
])
++ [
{modules.secrets.server.network.enable = true;}
];
};
inherit (inputs) nixos-rk3588;
baseSpecialArgs = genSpecialArgs system;
rk3588Pkgs = import nixos-rk3588.inputs.nixpkgs {inherit system;};
rk3588SpecialArgs = let
# using the same nixpkgs as nixos-rk3588
inherit (nixos-rk3588.inputs) nixpkgs;
# use aarch64-linux's native toolchain
pkgsKernel = import nixpkgs {inherit system;};
in
baseSpecialArgs
// {
inherit nixpkgs;
# Provide rk3588 inputs as special argument
rk3588 = {inherit nixpkgs pkgsKernel;};
};
rk3588SystemArgs =
modules
// args
// {
inherit (nixos-rk3588.inputs) nixpkgs; # or nixpkgs-unstable
specialArgs = rk3588SpecialArgs;
};
in {
nixosConfigurations.${name} = mylib.nixosSystem rk3588SystemArgs;
colmenaMeta = {
nodeSpecialArgs.${name} = rk3588SpecialArgs;
nodeNixpkgs.${name} = rk3588Pkgs;
};
colmena.${name} =
mylib.colmenaSystem
(rk3588SystemArgs // {inherit tags ssh-user;});
}
@@ -1,8 +0,0 @@
{
lib,
outputs,
}: let
hostsNames = builtins.attrNames outputs.nixosConfigurations;
expected = lib.genAttrs hostsNames (name: name);
in
expected
@@ -1,9 +0,0 @@
{
lib,
outputs,
}:
lib.genAttrs
(builtins.attrNames outputs.nixosConfigurations)
(
name: outputs.nixosConfigurations.${name}.config.networking.hostName
)
@@ -1,8 +0,0 @@
{
lib,
outputs,
}: let
hostsNames = builtins.attrNames outputs.nixosConfigurations;
expected = lib.genAttrs hostsNames (_: true);
in
expected
@@ -1,11 +0,0 @@
{
lib,
outputs,
}:
lib.genAttrs
(builtins.attrNames outputs.nixosConfigurations)
(
# test only if kernelPackages is set, to avoid build the kernel.
# name: outputs.nixosConfigurations.${name}.config.boot.kernelPackages.kernel.system
name: outputs.nixosConfigurations.${name}.config.boot.kernelPackages != null
)
+2 -2
View File
@@ -34,8 +34,8 @@
# modules for each supported system
nixosSystems = {
x86_64-linux = import ./x86_64-linux (args // {system = "x86_64-linux";});
aarch64-linux = import ./aarch64-linux (args // {system = "aarch64-linux";});
riscv64-linux = import ./riscv64-linux (args // {system = "riscv64-linux";});
# aarch64-linux = import ./aarch64-linux (args // {system = "aarch64-linux";});
# riscv64-linux = import ./riscv64-linux (args // {system = "riscv64-linux";});
};
darwinSystems = {
aarch64-darwin = import ./aarch64-darwin (args // {system = "aarch64-darwin";});
-38
View File
@@ -1,38 +0,0 @@
{
lib,
inputs,
...
} @ args: let
inherit (inputs) haumea;
# Contains all the flake outputs of this system architecture.
data = haumea.lib.load {
src = ./src;
inputs = args;
};
# nix file names is redundant, so we remove it.
dataWithoutPaths = builtins.attrValues data;
# Merge all the machine's data into a single attribute set.
outputs = {
nixosConfigurations = lib.attrsets.mergeAttrsList (map (it: it.nixosConfigurations or {}) dataWithoutPaths);
packages = lib.attrsets.mergeAttrsList (map (it: it.packages or {}) dataWithoutPaths);
# colmena contains some meta info, which need to be merged carefully.
colmenaMeta = {
nodeNixpkgs = lib.attrsets.mergeAttrsList (map (it: it.colmenaMeta.nodeNixpkgs or {}) dataWithoutPaths);
nodeSpecialArgs = lib.attrsets.mergeAttrsList (map (it: it.colmenaMeta.nodeSpecialArgs or {}) dataWithoutPaths);
};
# colmena's per-machine data.
colmena = lib.attrsets.mergeAttrsList (map (it: it.colmena or {}) dataWithoutPaths);
};
in
outputs
// {
inherit data; # for debugging purposes
# NixOS's unit tests.
evalTests = haumea.lib.loadEvalTests {
src = ./tests;
inputs = args // {inherit outputs;};
};
}
@@ -1,60 +0,0 @@
{
# NOTE: the args not used in this file CAN NOT be removed!
# because haumea pass argument lazily,
# and these arguments are used in the functions like `mylib.nixosSystem`, `mylib.colmenaSystem`, etc.
inputs,
lib,
mylib,
myvars,
system,
genSpecialArgs,
...
} @ args: let
# 森友 望未, Moritomo Nozomi
name = "nozomi";
tags = [name "riscv"];
ssh-user = "root";
modules = {
nixos-modules =
(map mylib.relativeToRoot [
"modules/nixos/server/server-riscv64.nix"
# host specific modules
"hosts/rolling-girls-${name}"
])
++ [
# cross-compilation this flake.
{nixpkgs.crossSystem.system = "riscv64-linux";}
];
};
inherit (inputs) nixos-licheepi4a;
baseSpecialArgs = genSpecialArgs system;
# using the same nixpkgs as nixos-licheepi4a to utilize the cross-compilation cache.
lpi4aPkgs = import nixos-licheepi4a.inputs.nixpkgs {inherit system;};
lpi4aSpecialArgs =
baseSpecialArgs
// {
inherit (nixos-licheepi4a.inputs) nixpkgs;
pkgsKernel = nixos-licheepi4a.packages.${system}.pkgsKernelCross;
}
// args;
lpi4aSystemArgs =
modules
// args
// {
inherit (nixos-licheepi4a.inputs) nixpkgs;
specialArgs = lpi4aSpecialArgs;
};
in {
nixosConfigurations.${name} = mylib.nixosSystem lpi4aSystemArgs;
colmenaMeta = {
nodeSpecialArgs.${name} = lpi4aSpecialArgs;
nodeNixpkgs.${name} = lpi4aPkgs;
};
colmena.${name} =
mylib.colmenaSystem
(lpi4aSystemArgs // {inherit tags ssh-user;});
}
@@ -1,60 +0,0 @@
{
# NOTE: the args not used in this file CAN NOT be removed!
# because haumea pass argument lazily,
# and these arguments are used in the functions like `mylib.nixosSystem`, `mylib.colmenaSystem`, etc.
inputs,
lib,
mylib,
myvars,
system,
genSpecialArgs,
...
} @ args: let
# 小坂 結季奈, Kosaka Yukina
name = "yukina";
tags = [name "riscv"];
ssh-user = "root";
modules = {
nixos-modules =
(map mylib.relativeToRoot [
"modules/nixos/server/server-riscv64.nix"
# host specific modules
"hosts/rolling-girls-${name}"
])
++ [
# cross-compilation this flake.
{nixpkgs.crossSystem.system = "riscv64-linux";}
];
};
inherit (inputs) nixos-licheepi4a;
baseSpecialArgs = genSpecialArgs system;
# using the same nixpkgs as nixos-licheepi4a to utilize the cross-compilation cache.
lpi4aPkgs = import nixos-licheepi4a.inputs.nixpkgs {inherit system;};
lpi4aSpecialArgs =
baseSpecialArgs
// {
inherit (nixos-licheepi4a.inputs) nixpkgs;
pkgsKernel = nixos-licheepi4a.packages.${system}.pkgsKernelCross;
}
// args;
lpi4aSystemArgs =
modules
// args
// {
inherit (nixos-licheepi4a.inputs) nixpkgs;
specialArgs = lpi4aSpecialArgs;
};
in {
nixosConfigurations.${name} = mylib.nixosSystem lpi4aSystemArgs;
colmenaMeta = {
nodeSpecialArgs.${name} = lpi4aSpecialArgs;
nodeNixpkgs.${name} = lpi4aPkgs;
};
colmena.${name} =
mylib.colmenaSystem
(lpi4aSystemArgs // {inherit tags ssh-user;});
}
@@ -1,8 +0,0 @@
{
lib,
outputs,
}: let
hostsNames = builtins.attrNames outputs.nixosConfigurations;
expected = lib.genAttrs hostsNames (name: name);
in
expected
@@ -1,9 +0,0 @@
{
lib,
outputs,
}:
lib.genAttrs
(builtins.attrNames outputs.nixosConfigurations)
(
name: outputs.nixosConfigurations.${name}.config.networking.hostName
)
@@ -29,6 +29,7 @@
{modules.secrets.server.application.enable = true;}
{modules.secrets.server.operation.enable = true;}
{modules.secrets.server.webserver.enable = true;}
{modules.secrets.server.storage.enable = true;}
];
home-modules = map mylib.relativeToRoot [
"home/linux/tui.nix"
+3 -3
View File
@@ -15,7 +15,7 @@ from pathlib import Path
NIX_DAEMON_PLIST = Path("/Library/LaunchDaemons/org.nixos.nix-daemon.plist")
NIX_DAEMON_NAME = "org.nixos.nix-daemon"
# http proxy provided by my homelab's bypass router
HTTP_PROXY = "http://192.168.5.179:7890"
HTTP_PROXY = "http://192.168.5.101:7890"
PLIST = plistlib.loads(NIX_DAEMON_PLIST.read_bytes())
@@ -55,5 +55,5 @@ def unset_proxy():
if __name__ == "__main__":
# set_proxy()
unset_proxy()
set_proxy()
# unset_proxy()
+20
View File
@@ -35,6 +35,7 @@ in {
server.operation.enable = mkEnableOption "NixOS Secrets for Operation Servers(Backup, Monitoring, etc)";
server.kubernetes.enable = mkEnableOption "NixOS Secrets for Kubernetes";
server.webserver.enable = mkEnableOption "NixOS Secrets for Web Servers(contains tls cert keys)";
server.storage.enable = mkEnableOption "NixOS Secrets for HDD Data's LUKS Encryption";
impermanence.enable = mkEnableOption "whether use impermanence and ephemeral root file system";
};
@@ -249,5 +250,24 @@ in {
};
};
})
(mkIf cfg.server.storage.enable {
age.secrets = {
"hdd-luks-crypt-key" = {
file = "${mysecrets}/hdd-luks-crypt-key.age";
mode = "0400";
owner = "root";
};
};
# place secrets in /etc/
environment.etc = {
"agenix/hdd-luks-crypt-key" = {
source = config.age.secrets."hdd-luks-crypt-key".path;
mode = "0400";
user = "root";
};
};
})
]);
}
+19
View File
@@ -3,4 +3,23 @@
userfullname = "Ryan Yin";
useremail = "xiaoyin_c@qq.com";
networking = import ./networking.nix {inherit lib;};
# generated by `mkpasswd -m scrypt`
initialHashedPassword = "$7$CU..../....KDvTIXqLTXpmCaoUy2yC9.$145eM358b7Q0sRXgEBvxctd5EAuEEdao57LmZjc05D.";
# Public Keys that can be used to login to all my PCs, Macbooks, and servers.
#
# Since its authority is so large, we must strengthen its security:
# 1. The corresponding private key must be:
# 1. Generated locally on every trusted client via:
# ```bash
# # KDF: bcrypt with 256 rounds, takes 2s on Apple M2):
# # Passphrase: digits + letters + symbols, 12+ chars
# ssh-keygen -t ed25519 -a 256 -C "ryan@xxx" -f ~/.ssh/xxx`
# ```
# 2. Never leave the device and never sent over the network.
# 2. Or just use hardware security keys like Yubikey/CanoKey.
sshAuthorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIKlN+Q/GxvwxDX/OAjJHaNFEznEN4Tw4E4TwqQu/eD6 ryan@idols-ai"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPoa9uEI/gR5+klqTQwvCgD6CD5vT5iD9YCNx2xNrH3B ryan@fern"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPwZ9MdotnyhxIJrI4gmVshExHiZOx+FGFhcW7BaYkfR ryan@harmonica"
];
}