Compare commits

..

No commits in common. "master" and "365d3e37359d202edb980e8d3d1ac81a59d3dfcf" have entirely different histories.

178 changed files with 10718 additions and 15517 deletions

View File

@ -1,19 +0,0 @@
name: Check Flake
on: [push]
env:
DEBIAN_FRONTEND: noninteractive
PATH: /run/current-system/sw/bin/
jobs:
check-flake:
runs-on: nixos
steps:
- name: Checkout the repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check Flake
run: nix flake check --all-systems --print-build-logs --log-format raw --show-trace

1
.gitignore vendored
View File

@ -1 +0,0 @@
result

View File

@ -1,27 +0,0 @@
# Lockfile utils
.PHONY: update-lockfile
update-lockfile:
nix flake update --commit-lock-file
.PHONY: update-lockfile-without-commit
update-lockfile-without-commit:
nix flake update
# Agenix utils
.PHONY: edit-secret
edit-secret:
cd secrets && agenix -e $(filter-out $@,$(MAKECMDGOALS))
.PHONY: rekey-secrets
rekey-secrets:
cd secrets && agenix -r
# NixOS utils
.PHONY: clean-old-nixos-profiles
clean-old-nixos-profiles:
doas nix-collect-garbage -d
# Garbage Collect
.PHONY: gc
gc:
nix store gc

View File

@ -1,11 +0,0 @@
# My NixOS configurations
### Source Layout
- `/common` - common configuration imported into all `/machines`
- `/boot` - config related to bootloaders, cpu microcode, and unlocking LUKS root disks over tor
- `/network` - config for tailscale, and NixOS container with automatic vpn tunneling via PIA
- `/pc` - config that a graphical PC should have. Have the `personal` role set in the machine's `properties.nix` to enable everthing.
- `/server` - config that creates new nixos services or extends existing ones to meet my needs
- `/machines` - all my NixOS machines along with their machine unique configuration for hardware and services
- `/kexec` - a special machine for generating minimal kexec images. Does not import `/common`
- `/secrets` - encrypted shared secrets unlocked through `/machines` ssh host keys

84
TODO.md
View File

@ -1,84 +0,0 @@
# A place for brain dump ideas maybe to be taken off of the shelve one day
### NixOS webtools
- Better options search https://mynixos.com/options/services
### Interesting ideas for restructuring nixos config
- https://github.com/gytis-ivaskevicius/flake-utils-plus
- https://github.com/divnix/digga/tree/main/examples/devos
- https://digga.divnix.com/
- https://nixos.wiki/wiki/Comparison_of_NixOS_setups
### Housekeeping
- Cleanup the line between hardware-configuration.nix and configuration.nix in machine config
- remove `options.currentSystem`
- allow `hostname` option for webservices to be null to disable configuring nginx
### NAS
- safely turn off NAS on power disconnect
### Shell Comands
- tailexitnode = `sudo tailscale up --exit-node=<exit-node-ip> --exit-node-allow-lan-access=true`
### Services
- setup archivebox
- radio https://tildegit.org/tilderadio/site
- music
- mopidy
- use the jellyfin plugin?
- navidrome
- spotify secrets for navidrome
- picard for music tagging
- alternative music software
- https://www.smarthomebeginner.com/best-music-server-software-options/
- https://funkwhale.audio/
- https://github.com/epoupon/lms
- https://github.com/benkaiser/stretto
- https://github.com/blackcandy-org/black_candy
- https://github.com/koel/koel
- https://airsonic.github.io/
- https://ampache.org/
- replace nextcloud with seafile
### Archive
- email
- https://github.com/Disassembler0/dovecot-archive/blob/main/src/dovecot_archive.py
- http://kb.unixservertech.com/software/dovecot/archiveserver
### Paranoia
- https://christine.website/blog/paranoid-nixos-2021-07-18
- https://nixos.wiki/wiki/Impermanence
# Setup CI
- CI
- hydra
- https://docs.cachix.org/continuous-integration-setup/
- Binary Cache
- Maybe use cachix https://gvolpe.com/blog/nixos-binary-cache-ci/
- Self hosted binary cache? https://www.tweag.io/blog/2019-11-21-untrusted-ci/
- https://github.com/edolstra/nix-serve
- https://nixos.wiki/wiki/Binary_Cache
- https://discourse.nixos.org/t/introducing-attic-a-self-hostable-nix-binary-cache-server/24343
- Both
- https://garnix.io/
- https://nixbuild.net
# Secrets
- consider using headscale
- Replace luks over tor for remote unlock with luks over tailscale using ephemeral keys
- Rollover luks FDE passwords
- /secrets on personal computers should only be readable using a trusted ssh key, preferably requiring a yubikey
- Rollover shared yubikey secrets
- offsite backup yubikey, pw db, and ssh key with /secrets access
### Misc
- for automated kernel upgrades on luks systems, need to kexec with initrd that contains luks key
- https://github.com/flowztul/keyexec/blob/master/etc/default/kexec-cryptroot
- https://github.com/pop-os/system76-scheduler
- improve email a little bit https://helloinbox.email
- remap razer keys https://github.com/sezanzeb/input-remapper
### Future Interests (upon merge into nixpkgs)
- nixos/thelounge: add users option https://github.com/NixOS/nixpkgs/pull/157477
- glorytun: init at 0.3.4 https://github.com/NixOS/nixpkgs/pull/153356

View File

@ -1,15 +0,0 @@
{ config, lib, ... }:
# Modify auto-update so that it pulls a flake
let
cfg = config.system.autoUpgrade;
in
{
config = lib.mkIf cfg.enable {
system.autoUpgrade = {
flake = "git+https://git.neet.dev/zuckerberg/nix-config.git";
flags = [ "--recreate-lock-file" "--no-write-lock-file" ]; # ignore lock file, just pull the latest
};
};
}

View File

@ -1,78 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.backup;
hostname = config.networking.hostName;
mkRespository = group: "s3:s3.us-west-004.backblazeb2.com/D22TgIt0-main-backup/${group}";
mkBackup = group: paths: {
repository = mkRespository group;
inherit paths;
initialize = true;
timerConfig = {
OnCalendar = "daily";
RandomizedDelaySec = "1h";
};
extraBackupArgs = [
''--exclude-if-present ".nobackup"''
];
pruneOpts = [
"--keep-daily 7" # one backup for each of the last n days
"--keep-weekly 5" # one backup for each of the last n weeks
"--keep-monthly 12" # one backup for each of the last n months
"--keep-yearly 75" # one backup for each of the last n years
];
environmentFile = "/run/agenix/backblaze-s3-backups";
passwordFile = "/run/agenix/restic-password";
};
# example usage: "sudo restic_samba unlock" (removes lockfile)
mkResticGroupCmd = group: pkgs.writeShellScriptBin "restic_${group}" ''
if [ "$EUID" -ne 0 ]
then echo "Run as root"
exit
fi
. /run/agenix/backblaze-s3-backups
export AWS_SECRET_ACCESS_KEY
export AWS_ACCESS_KEY_ID
export RESTIC_PASSWORD_FILE=/run/agenix/restic-password
export RESTIC_REPOSITORY="${mkRespository group}"
exec ${pkgs.restic}/bin/restic "$@"
'';
in
{
options.backup = {
group = lib.mkOption {
default = null;
type = lib.types.nullOr (lib.types.attrsOf (lib.types.submodule {
options = {
paths = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
Paths to backup
'';
};
};
}));
};
};
config = lib.mkIf (cfg.group != null) {
services.restic.backups = lib.concatMapAttrs
(group: groupCfg: {
${group} = mkBackup group groupCfg.paths;
})
cfg.group;
age.secrets.backblaze-s3-backups.file = ../secrets/backblaze-s3-backups.age;
age.secrets.restic-password.file = ../secrets/restic-password.age;
environment.systemPackages = map mkResticGroupCmd (builtins.attrNames cfg.group);
};
}

View File

@ -1,17 +0,0 @@
{ config, lib, ... }:
{
nix = {
settings = {
substituters = [
"https://cache.nixos.org/"
"https://nix-community.cachix.org"
"http://s0.koi-bebop.ts.net:5000"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"s0.koi-bebop.ts.net:OjbzD86YjyJZpCp9RWaQKANaflcpKhtzBMNP8I2aPUU="
];
};
};
}

View File

@ -3,29 +3,28 @@
with lib;
let
cfg = config.bios;
in
{
in {
options.bios = {
enable = mkEnableOption "enable bios boot";
device = mkOption {
type = types.str;
};
configurationLimit = mkOption {
default = 20;
type = types.int;
};
};
config = mkIf cfg.enable {
# Enable microcode
firmware.x86_64.enable = true;
# Use GRUB 2 for BIOS
boot.loader = {
timeout = 2;
grub = {
enable = true;
device = cfg.device;
version = 2;
useOSProber = true;
configurationLimit = cfg.configurationLimit;
configurationLimit = 20;
theme = pkgs.nixos-grub2-theme;
};
};
};
}
}

View File

@ -1,10 +0,0 @@
{ lib, config, pkgs, ... }:
{
imports = [
./firmware.nix
./efi.nix
./bios.nix
./remote-luks-unlock.nix
];
}

View File

@ -3,27 +3,26 @@
with lib;
let
cfg = config.efi;
in
{
in {
options.efi = {
enable = mkEnableOption "enable efi boot";
configurationLimit = mkOption {
default = 20;
type = types.int;
};
};
config = mkIf cfg.enable {
# Enable microcode
firmware.x86_64.enable = true;
# Use GRUB2 for EFI
boot.loader = {
efi.canTouchEfiVariables = true;
timeout = 2;
grub = {
enable = true;
device = "nodev";
version = 2;
efiSupport = true;
useOSProber = true;
# memtest86.enable = true;
configurationLimit = cfg.configurationLimit;
# memtest86.enable = true;
configurationLimit = 20;
theme = pkgs.nixos-grub2-theme;
};
};

View File

@ -3,8 +3,7 @@
with lib;
let
cfg = config.firmware;
in
{
in {
options.firmware.x86_64 = {
enable = mkEnableOption "enable x86_64 firmware";
};
@ -15,4 +14,4 @@ in
};
# services.fwupd.enable = true;
}
}

101
common/boot/luks.nix Normal file
View File

@ -0,0 +1,101 @@
{ config, pkgs, lib, ... }:
let
cfg = config.luks;
in {
options.luks = {
enable = lib.mkEnableOption "enable luks root remote decrypt over ssh/tor";
device = {
name = lib.mkOption {
type = lib.types.str;
default = "enc-pv";
};
path = lib.mkOption {
type = lib.types.either lib.types.str lib.types.path;
};
allowDiscards = lib.mkOption {
type = lib.types.bool;
default = false;
};
};
sshHostKeys = lib.mkOption {
type = lib.types.listOf (lib.types.either lib.types.str lib.types.path);
default = [
"/secret/ssh_host_rsa_key"
"/secret/ssh_host_ed25519_key"
];
};
sshAuthorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = config.users.users.googlebot.openssh.authorizedKeys.keys;
};
onionConfig = lib.mkOption {
type = lib.types.path;
default = /secret/onion;
};
kernelModules = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "e1000" "e1000e" "virtio_pci" "r8169" ];
};
};
config = lib.mkIf cfg.enable {
boot.initrd.luks.devices.${cfg.device.name} = {
device = cfg.device.path;
allowDiscards = cfg.device.allowDiscards;
};
# Unlock LUKS disk over ssh
boot.initrd.network.enable = true;
boot.initrd.kernelModules = cfg.kernelModules;
boot.initrd.network.ssh = {
enable = true;
port = 22;
hostKeys = cfg.sshHostKeys;
authorizedKeys = cfg.sshAuthorizedKeys;
};
boot.initrd.postDeviceCommands = ''
echo 'waiting for root device to be opened...'
mkfifo /crypt-ramfs/passphrase
echo /crypt-ramfs/passphrase >> /dev/null
'';
# Make machine accessable over tor for boot unlock
boot.initrd.secrets = {
"/etc/tor/onion/bootup" = cfg.onionConfig;
};
boot.initrd.extraUtilsCommands = ''
copy_bin_and_libs ${pkgs.tor}/bin/tor
copy_bin_and_libs ${pkgs.haveged}/bin/haveged
'';
# start tor during boot process
boot.initrd.network.postCommands = let
torRc = (pkgs.writeText "tor.rc" ''
DataDirectory /etc/tor
SOCKSPort 127.0.0.1:9050 IsolateDestAddr
SOCKSPort 127.0.0.1:9063
HiddenServiceDir /etc/tor/onion/bootup
HiddenServicePort 22 127.0.0.1:22
'');
in ''
# Add nice prompt for giving LUKS passphrase over ssh
echo 'read -s -p "Unlock Passphrase: " passphrase && echo $passphrase > /crypt-ramfs/passphrase && exit' >> /root/.profile
echo "tor: preparing onion folder"
# have to do this otherwise tor does not want to start
chmod -R 700 /etc/tor
echo "make sure localhost is up"
ip a a 127.0.0.1/8 dev lo
ip link set lo up
echo "haveged: starting haveged"
haveged -F &
echo "tor: starting tor"
tor -f ${torRc} --verify-config
tor -f ${torRc} &
'';
};
}

View File

@ -1,96 +0,0 @@
{ config, pkgs, lib, ... }:
# TODO: use tailscale instead of tor https://gist.github.com/antifuchs/e30d58a64988907f282c82231dde2cbc
let
cfg = config.remoteLuksUnlock;
in
{
options.remoteLuksUnlock = {
enable = lib.mkEnableOption "enable luks root remote decrypt over ssh/tor";
enableTorUnlock = lib.mkOption {
type = lib.types.bool;
default = cfg.enable;
description = "Make machine accessable over tor for ssh boot unlock";
};
sshHostKeys = lib.mkOption {
type = lib.types.listOf (lib.types.either lib.types.str lib.types.path);
default = [
"/secret/ssh_host_rsa_key"
"/secret/ssh_host_ed25519_key"
];
};
sshAuthorizedKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = config.users.users.googlebot.openssh.authorizedKeys.keys;
};
onionConfig = lib.mkOption {
type = lib.types.path;
default = /secret/onion;
};
kernelModules = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "e1000" "e1000e" "virtio_pci" "r8169" ];
};
};
config = lib.mkIf cfg.enable {
# Unlock LUKS disk over ssh
boot.initrd.network.enable = true;
boot.initrd.kernelModules = cfg.kernelModules;
boot.initrd.network.ssh = {
enable = true;
port = 22;
hostKeys = cfg.sshHostKeys;
authorizedKeys = cfg.sshAuthorizedKeys;
};
boot.initrd.postDeviceCommands = ''
echo 'waiting for root device to be opened...'
mkfifo /crypt-ramfs/passphrase
echo /crypt-ramfs/passphrase >> /dev/null
'';
boot.initrd.secrets = lib.mkIf cfg.enableTorUnlock {
"/etc/tor/onion/bootup" = cfg.onionConfig;
};
boot.initrd.extraUtilsCommands = lib.mkIf cfg.enableTorUnlock ''
copy_bin_and_libs ${pkgs.tor}/bin/tor
copy_bin_and_libs ${pkgs.haveged}/bin/haveged
'';
boot.initrd.network.postCommands = lib.mkMerge [
(
''
# Add nice prompt for giving LUKS passphrase over ssh
echo 'read -s -p "Unlock Passphrase: " passphrase && echo $passphrase > /crypt-ramfs/passphrase && exit' >> /root/.profile
''
)
(
let torRc = (pkgs.writeText "tor.rc" ''
DataDirectory /etc/tor
SOCKSPort 127.0.0.1:9050 IsolateDestAddr
SOCKSPort 127.0.0.1:9063
HiddenServiceDir /etc/tor/onion/bootup
HiddenServicePort 22 127.0.0.1:22
''); in
lib.mkIf cfg.enableTorUnlock ''
echo "tor: preparing onion folder"
# have to do this otherwise tor does not want to start
chmod -R 700 /etc/tor
echo "make sure localhost is up"
ip a a 127.0.0.1/8 dev lo
ip link set lo up
echo "haveged: starting haveged"
haveged -F &
echo "tor: starting tor"
tor -f ${torRc} --verify-config
tor -f ${torRc} &
''
)
];
};
}

51
common/common.nix Normal file
View File

@ -0,0 +1,51 @@
{ config, pkgs, ... }:
{
imports = [
./flakes.nix
./pia.nix
./zerotier.nix
./boot/firmware.nix
./boot/efi.nix
./boot/bios.nix
./boot/luks.nix
./server/nginx.nix
./server/thelounge.nix
./server/mumble.nix
./server/icecast.nix
./server/nginx-stream.nix
./server/matrix.nix
./server/zerobin.nix
./server/gitea.nix
./server/privatebin/privatebin.nix
./pc/de.nix
];
system.stateVersion = "20.09";
networking.useDHCP = false;
time.timeZone = "America/New_York";
i18n.defaultLocale = "en_US.UTF-8";
services.openssh.enable = true;
environment.systemPackages = with pkgs; [
wget kakoune htop git dnsutils tmux nethogs iotop
];
nixpkgs.config.allowUnfree = true;
users.mutableUsers = false;
users.users.googlebot = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMVR/R3ZOsv7TZbICGBCHdjh1NDT8SnswUyINeJOC7QG"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0dcqL/FhHmv+a1iz3f9LJ48xubO7MZHy35rW9SZOYM"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO0VFnn3+Mh0nWeN92jov81qNE9fpzTAHYBphNoY7HUx" # reg
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHSkKiRUUmnErOKGx81nyge/9KqjkPh8BfDk0D3oP586" # nat
];
hashedPassword = "$6$TuDO46rILr$gkPUuLKZe3psexhs8WFZMpzgEBGksE.c3Tjh1f8sD0KMC4oV89K2pqAABfl.Lpxu2jVdr5bgvR5cWnZRnji/r/";
};
}

View File

@ -1,106 +0,0 @@
{ config, pkgs, lib, ... }:
{
imports = [
./backups.nix
./binary-cache.nix
./flakes.nix
./auto-update.nix
./shell.nix
./network
./boot
./server
./pc
./machine-info
./nix-builder.nix
./ssh.nix
];
nix.flakes.enable = true;
system.stateVersion = "23.11";
networking.useDHCP = lib.mkDefault true;
networking.firewall.enable = true;
networking.firewall.allowPing = true;
time.timeZone = "America/Los_Angeles";
i18n = {
defaultLocale = "en_US.UTF-8";
extraLocaleSettings = {
LANGUAGE = "en_US.UTF-8";
LC_ALL = "en_US.UTF-8";
};
};
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
};
};
programs.mosh.enable = true;
environment.systemPackages = with pkgs; [
wget
kakoune
htop
git
git-lfs
dnsutils
tmux
nethogs
iotop
pciutils
usbutils
killall
screen
micro
helix
lm_sensors
picocom
lf
gnumake
tree
];
nixpkgs.config.allowUnfree = true;
users.mutableUsers = false;
users.users.googlebot = {
isNormalUser = true;
extraGroups = [
"wheel"
"dialout" # serial
];
shell = pkgs.fish;
openssh.authorizedKeys.keys = config.machines.ssh.userKeys;
hashedPassword = "$6$TuDO46rILr$gkPUuLKZe3psexhs8WFZMpzgEBGksE.c3Tjh1f8sD0KMC4oV89K2pqAABfl.Lpxu2jVdr5bgvR5cWnZRnji/r/";
uid = 1000;
};
users.users.root = {
openssh.authorizedKeys.keys = config.machines.ssh.deployKeys;
};
nix.settings = {
trusted-users = [ "root" "googlebot" ];
};
# don't use sudo
security.doas.enable = true;
security.sudo.enable = false;
security.doas.extraRules = [
# don't ask for password every time
{ groups = [ "wheel" ]; persist = true; }
];
nix.gc.automatic = true;
security.acme.acceptTerms = true;
security.acme.defaults.email = "zuckerberg@neet.dev";
# Enable Desktop Environment if this is a PC (machine role is "personal")
de.enable = (
builtins.elem config.networking.hostName config.machines.roles.personal
);
}

View File

@ -1,18 +1,21 @@
{ lib, pkgs, config, ... }:
{ lib, pkgs, config, inputs, ... }:
with lib;
let
cfg = config.nix.flakes;
in
{
in {
options.nix.flakes = {
enable = mkEnableOption "use nix flakes";
};
config = mkIf cfg.enable {
nix = {
package = pkgs.nixFlakes;
extraOptions = ''
experimental-features = nix-command flakes
'';
# pin nixpkgs for system commands such as "nix shell"
registry.nixpkgs.flake = inputs.nixpkgs;
};
};
}

View File

@ -1,200 +0,0 @@
# Gathers info about each machine to constuct overall configuration
# Ex: Each machine already trusts each others SSH fingerprint already
{ config, lib, pkgs, ... }:
let
machines = config.machines.hosts;
in
{
imports = [
./ssh.nix
./roles.nix
];
options.machines = {
hosts = lib.mkOption {
type = lib.types.attrsOf
(lib.types.submodule {
options = {
hostNames = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
List of hostnames for this machine. The first one is the default so it is the target of deployments.
Used for automatically trusting hosts for ssh connections.
'';
};
arch = lib.mkOption {
type = lib.types.enum [ "x86_64-linux" "aarch64-linux" ];
description = ''
The architecture of this machine.
'';
};
systemRoles = lib.mkOption {
type = lib.types.listOf lib.types.str; # TODO: maybe use an enum?
description = ''
The set of roles this machine holds. Affects secrets available. (TODO add service config as well using this info)
'';
};
hostKey = lib.mkOption {
type = lib.types.str;
description = ''
The system ssh host key of this machine. Used for automatically trusting hosts for ssh connections
and for decrypting secrets with agenix.
'';
};
remoteUnlock = lib.mkOption {
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
hostKey = lib.mkOption {
type = lib.types.str;
description = ''
The system ssh host key of this machine used for luks boot unlocking only.
'';
};
clearnetHost = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.str;
description = ''
The hostname resolvable over clearnet used to luks boot unlock this machine
'';
};
onionHost = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.str;
description = ''
The hostname resolvable over tor used to luks boot unlock this machine
'';
};
};
});
};
userKeys = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.str;
description = ''
The list of user keys. Each key here can be used to log into all other systems as `googlebot`.
TODO: consider auto populating other programs that use ssh keys such as gitea
'';
};
deployKeys = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.str;
description = ''
The list of deployment keys. Each key here can be used to log into all other systems as `root`.
'';
};
configurationPath = lib.mkOption {
type = lib.types.path;
description = ''
The path to this machine's configuration directory.
'';
};
};
});
};
};
config = {
assertions = (lib.concatLists (lib.mapAttrsToList
(
name: cfg: [
{
assertion = builtins.length cfg.hostNames > 0;
message = ''
Error with config for ${name}
There must be at least one hostname.
'';
}
{
assertion = builtins.length cfg.systemRoles > 0;
message = ''
Error with config for ${name}
There must be at least one system role.
'';
}
{
assertion = cfg.remoteUnlock == null || cfg.remoteUnlock.hostKey != cfg.hostKey;
message = ''
Error with config for ${name}
Unlock hostkey and hostkey cannot be the same because unlock hostkey is in /boot, unencrypted.
'';
}
{
assertion = cfg.remoteUnlock == null || (cfg.remoteUnlock.clearnetHost != null || cfg.remoteUnlock.onionHost != null);
message = ''
Error with config for ${name}
At least one of clearnet host or onion host must be defined.
'';
}
{
assertion = cfg.remoteUnlock == null || cfg.remoteUnlock.clearnetHost == null || builtins.elem cfg.remoteUnlock.clearnetHost cfg.hostNames == false;
message = ''
Error with config for ${name}
Clearnet unlock hostname cannot be in the list of hostnames for security reasons.
'';
}
{
assertion = cfg.remoteUnlock == null || cfg.remoteUnlock.onionHost == null || lib.strings.hasSuffix ".onion" cfg.remoteUnlock.onionHost;
message = ''
Error with config for ${name}
Tor unlock hostname must be an onion address.
'';
}
{
assertion = builtins.elem "personal" cfg.systemRoles || builtins.length cfg.userKeys == 0;
message = ''
Error with config for ${name}
There must be at least one userkey defined for personal machines.
'';
}
{
assertion = builtins.elem "deploy" cfg.systemRoles || builtins.length cfg.deployKeys == 0;
message = ''
Error with config for ${name}
Only deploy machines are allowed to have deploy keys for security reasons.
'';
}
]
)
machines));
# Set per machine properties automatically using each of their `properties.nix` files respectively
machines.hosts =
let
properties = dir: lib.concatMapAttrs
(name: path: {
${name} =
import path
//
{ configurationPath = builtins.dirOf path; };
})
(propertiesFiles dir);
propertiesFiles = dir:
lib.foldl (lib.mergeAttrs) { } (propertiesFiles' dir);
propertiesFiles' = dir:
let
propFiles = lib.filter (p: baseNameOf p == "properties.nix") (lib.filesystem.listFilesRecursive dir);
dirName = path: builtins.baseNameOf (builtins.dirOf path);
in
builtins.map (p: { "${dirName p}" = p; }) propFiles;
in
properties ../../machines;
};
}

View File

@ -1,15 +0,0 @@
# Allows getting machine-info outside the scope of nixos configuration
{ nixpkgs ? import <nixpkgs> { }
, assertionsModule ? <nixpkgs/nixos/modules/misc/assertions.nix>
}:
{
machines =
(nixpkgs.lib.evalModules {
modules = [
./default.nix
assertionsModule
];
}).config.machines;
}

View File

@ -1,19 +0,0 @@
{ config, lib, ... }:
# Maps roles to their hosts
{
options.machines.roles = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
};
config = {
machines.roles = lib.zipAttrs
(lib.mapAttrsToList
(host: cfg:
lib.foldl (lib.mergeAttrs) { }
(builtins.map (role: { ${role} = host; })
cfg.systemRoles))
config.machines.hosts);
};
}

View File

@ -1,44 +0,0 @@
{ config, lib, ... }:
let
machines = config.machines;
sshkeys = keyType: lib.foldl (l: cfg: l ++ cfg.${keyType}) [ ] (builtins.attrValues machines.hosts);
in
{
options.machines.ssh = {
userKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
List of user keys aggregated from all machines.
'';
};
deployKeys = lib.mkOption {
default = [ ];
type = lib.types.listOf lib.types.str;
description = ''
List of deploy keys aggregated from all machines.
'';
};
hostKeysByRole = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.str);
description = ''
Machine host keys divided into their roles.
'';
};
};
config = {
machines.ssh.userKeys = sshkeys "userKeys";
machines.ssh.deployKeys = sshkeys "deployKeys";
machines.ssh.hostKeysByRole = lib.mapAttrs
(role: hosts:
builtins.map
(host: machines.hosts.${host}.hostKey)
hosts)
machines.roles;
};
}

View File

@ -1,43 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQw
MzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
bmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVk
hjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULN
De4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9K
V2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ
25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SND
fCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZyl
p7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7p
Kwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzj
tRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wi
jSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNz
meGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz
1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNV
HQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRt
yWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
aW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCt
pXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dv
Em89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1G
tF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZu
LfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs
6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj3
5xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnX
JUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJ
iyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l
8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW
+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ=
-----END CERTIFICATE-----

View File

@ -1,23 +0,0 @@
{ config, lib, ... }:
with lib;
let
cfg = config.networking;
in
{
imports = [
./pia-openvpn.nix
./pia-wireguard.nix
./ping.nix
./tailscale.nix
./vpn.nix
];
options.networking.ip_forward = mkEnableOption "Enable ip forwarding";
config = mkIf cfg.ip_forward {
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
};
}

View File

@ -1,113 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.pia.openvpn;
vpnfailsafe = pkgs.stdenv.mkDerivation {
pname = "vpnfailsafe";
version = "0.0.1";
src = ./.;
installPhase = ''
mkdir -p $out
cp vpnfailsafe.sh $out/vpnfailsafe.sh
sed -i 's|getent|${pkgs.getent}/bin/getent|' $out/vpnfailsafe.sh
'';
};
in
{
options.pia.openvpn = {
enable = lib.mkEnableOption "Enable private internet access";
server = lib.mkOption {
type = lib.types.str;
default = "us-washingtondc.privacy.network";
example = "swiss.privacy.network";
};
};
config = lib.mkIf cfg.enable {
services.openvpn = {
servers = {
pia = {
config = ''
client
dev tun
proto udp
remote ${cfg.server} 1198
resolv-retry infinite
nobind
persist-key
persist-tun
cipher aes-128-cbc
auth sha1
tls-client
remote-cert-tls server
auth-user-pass
compress
verb 1
reneg-sec 0
<crl-verify>
-----BEGIN X509 CRL-----
MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
aW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZa
MCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG
9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5
jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EW
B4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Re
ze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA
5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqy
MR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A==
-----END X509 CRL-----
</crl-verify>
<ca>
-----BEGIN CERTIFICATE-----
MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1
MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD
L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX
lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp
cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/
8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB
/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC
OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL
y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO
sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM
b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G
A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg
SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz
czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j
b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn
a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU
ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3
7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC
GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz
1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt
YDQ8z9v+DMO6iwyIDRiU
-----END CERTIFICATE-----
</ca>
disable-occ
auth-user-pass /run/agenix/pia-login.conf
'';
autoStart = true;
up = "${vpnfailsafe}/vpnfailsafe.sh";
down = "${vpnfailsafe}/vpnfailsafe.sh";
};
};
};
age.secrets."pia-login.conf".file = ../../secrets/pia-login.age;
};
}

View File

@ -1,363 +0,0 @@
{ config, lib, pkgs, ... }:
# Server list:
# https://serverlist.piaservers.net/vpninfo/servers/v6
# Reference materials:
# https://github.com/pia-foss/manual-connections
# https://github.com/thrnz/docker-wireguard-pia/blob/master/extra/wg-gen.sh
# TODO handle potential errors (or at least print status, success, and failures to the console)
# TODO parameterize names of systemd services so that multiple wg VPNs could coexist in theory easier
# TODO implement this module such that the wireguard VPN doesn't have to live in a container
# TODO don't add forward rules if the PIA port is the same as cfg.forwardedPort
# TODO verify signatures of PIA responses
# TODO `RuntimeMaxSec = "30d";` for pia-vpn-wireguard-init isn't allowed per the systemd logs. Find alternative.
with builtins;
with lib;
let
cfg = config.pia.wireguard;
getPIAToken = ''
PIA_USER=`sed '1q;d' /run/agenix/pia-login.conf`
PIA_PASS=`sed '2q;d' /run/agenix/pia-login.conf`
# PIA_TOKEN only lasts 24hrs
PIA_TOKEN=`curl -s -u "$PIA_USER:$PIA_PASS" https://www.privateinternetaccess.com/gtoken/generateToken | jq -r '.token'`
'';
chooseWireguardServer = ''
servers=$(mktemp)
servers_json=$(mktemp)
curl -s "https://serverlist.piaservers.net/vpninfo/servers/v6" > "$servers"
# extract json part only
head -n 1 "$servers" | tr -d '\n' > "$servers_json"
echo "Available location ids:" && jq '.regions | .[] | {name, id, port_forward}' "$servers_json"
# Some locations have multiple servers available. Pick a random one.
totalservers=$(jq -r '.regions | .[] | select(.id=="'${cfg.serverLocation}'") | .servers.wg | length' "$servers_json")
if ! [[ "$totalservers" =~ ^[0-9]+$ ]] || [ "$totalservers" -eq 0 ] 2>/dev/null; then
echo "Location \"${cfg.serverLocation}\" not found."
exit 1
fi
serverindex=$(( RANDOM % totalservers))
WG_HOSTNAME=$(jq -r '.regions | .[] | select(.id=="'${cfg.serverLocation}'") | .servers.wg | .['$serverindex'].cn' "$servers_json")
WG_SERVER_IP=$(jq -r '.regions | .[] | select(.id=="'${cfg.serverLocation}'") | .servers.wg | .['$serverindex'].ip' "$servers_json")
WG_SERVER_PORT=$(jq -r '.groups.wg | .[0] | .ports | .[0]' "$servers_json")
# write chosen server
rm -f /tmp/${cfg.interfaceName}-server.conf
touch /tmp/${cfg.interfaceName}-server.conf
chmod 700 /tmp/${cfg.interfaceName}-server.conf
echo "$WG_HOSTNAME" >> /tmp/${cfg.interfaceName}-server.conf
echo "$WG_SERVER_IP" >> /tmp/${cfg.interfaceName}-server.conf
echo "$WG_SERVER_PORT" >> /tmp/${cfg.interfaceName}-server.conf
rm $servers_json $servers
'';
getChosenWireguardServer = ''
WG_HOSTNAME=`sed '1q;d' /tmp/${cfg.interfaceName}-server.conf`
WG_SERVER_IP=`sed '2q;d' /tmp/${cfg.interfaceName}-server.conf`
WG_SERVER_PORT=`sed '3q;d' /tmp/${cfg.interfaceName}-server.conf`
'';
refreshPIAPort = ''
${getChosenWireguardServer}
signature=`sed '1q;d' /tmp/${cfg.interfaceName}-port-renewal`
payload=`sed '2q;d' /tmp/${cfg.interfaceName}-port-renewal`
bind_port_response=`curl -Gs -m 5 --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "payload=$payload" --data-urlencode "signature=$signature" "https://$WG_HOSTNAME:19999/bindPort"`
'';
portForwarding = cfg.forwardPortForTransmission || cfg.forwardedPort != null;
containerServiceName = "container@${config.vpn-container.containerName}.service";
in
{
options.pia.wireguard = {
enable = mkEnableOption "Enable private internet access";
badPortForwardPorts = mkOption {
type = types.listOf types.port;
description = ''
Ports that will not be accepted from PIA.
If PIA assigns a port from this list, the connection is aborted since we cannot ask for a different port.
This is used to guarantee we are not assigned a port that is used by a service we do not want exposed.
'';
};
wireguardListenPort = mkOption {
type = types.port;
description = "The port wireguard listens on for this VPN connection";
default = 51820;
};
serverLocation = mkOption {
type = types.str;
default = "swiss";
};
interfaceName = mkOption {
type = types.str;
default = "piaw";
};
forwardedPort = mkOption {
type = types.nullOr types.port;
description = "The port to redirect port forwarded TCP VPN traffic too";
default = null;
};
forwardPortForTransmission = mkEnableOption "PIA port forwarding for transmission should be performed.";
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.forwardPortForTransmission != (cfg.forwardedPort != null);
message = ''
The PIA forwarded port cannot simultaneously be used by transmission and redirected to another port.
'';
}
];
# mounts used to pass the connection parameters to the container
# the container doesn't have internet until it uses these parameters so it cannot fetch them itself
vpn-container.mounts = [
"/tmp/${cfg.interfaceName}.conf"
"/tmp/${cfg.interfaceName}-server.conf"
"/tmp/${cfg.interfaceName}-address.conf"
];
# The container takes ownership of the wireguard interface on its startup
containers.vpn.interfaces = [ cfg.interfaceName ];
# TODO: while this is much better than "loose" networking, it seems to have issues with firewall restarts
# allow traffic for wireguard interface to pass since wireguard trips up rpfilter
# networking.firewall = {
# extraCommands = ''
# ip46tables -t raw -I nixos-fw-rpfilter -p udp -m udp --sport ${toString cfg.wireguardListenPort} -j RETURN
# ip46tables -t raw -I nixos-fw-rpfilter -p udp -m udp --dport ${toString cfg.wireguardListenPort} -j RETURN
# '';
# extraStopCommands = ''
# ip46tables -t raw -D nixos-fw-rpfilter -p udp -m udp --sport ${toString cfg.wireguardListenPort} -j RETURN || true
# ip46tables -t raw -D nixos-fw-rpfilter -p udp -m udp --dport ${toString cfg.wireguardListenPort} -j RETURN || true
# '';
# };
networking.firewall.checkReversePath = "loose";
systemd.services.pia-vpn-wireguard-init = {
description = "Creates PIA VPN Wireguard Interface";
wants = [ "network-online.target" ];
after = [ "network.target" "network-online.target" ];
before = [ containerServiceName ];
requiredBy = [ containerServiceName ];
partOf = [ containerServiceName ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ wireguard-tools jq curl iproute2 iputils ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
# restart once a month; PIA forwarded port expires after two months
# because the container is "PartOf" this unit, it gets restarted too
RuntimeMaxSec = "30d";
};
script = ''
echo Waiting for internet...
while ! ping -c 1 -W 1 1.1.1.1; do
sleep 1
done
# Prepare to connect by generating wg secrets and auth'ing with PIA since the container
# cannot do without internet to start with. NAT'ing the host's internet would address this
# issue but is not ideal because then leaking network outside of the VPN is more likely.
${chooseWireguardServer}
${getPIAToken}
# generate wireguard keys
privKey=$(wg genkey)
pubKey=$(echo "$privKey" | wg pubkey)
# authorize our WG keys with the PIA server we are about to connect to
wireguard_json=`curl -s -G --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "pt=$PIA_TOKEN" --data-urlencode "pubkey=$pubKey" https://$WG_HOSTNAME:$WG_SERVER_PORT/addKey`
# create wg-quick config file
rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
touch /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
chmod 700 /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
echo "
[Interface]
# Address = $(echo "$wireguard_json" | jq -r '.peer_ip')
PrivateKey = $privKey
ListenPort = ${toString cfg.wireguardListenPort}
[Peer]
PersistentKeepalive = 25
PublicKey = $(echo "$wireguard_json" | jq -r '.server_key')
AllowedIPs = 0.0.0.0/0
Endpoint = $WG_SERVER_IP:$(echo "$wireguard_json" | jq -r '.server_port')
" >> /tmp/${cfg.interfaceName}.conf
# create file storing the VPN ip address PIA assigned to us
echo "$wireguard_json" | jq -r '.peer_ip' >> /tmp/${cfg.interfaceName}-address.conf
# Create wg interface now so it inherits from the namespace with internet access
# the container will handle actually connecting the interface since that info is
# not preserved upon moving into the container's networking namespace
# Roughly following this guide https://www.wireguard.com/netns/#ordinary-containerization
[[ -z $(ip link show dev ${cfg.interfaceName} 2>/dev/null) ]] || exit
ip link add ${cfg.interfaceName} type wireguard
'';
preStop = ''
# cleanup wireguard interface
ip link del ${cfg.interfaceName}
rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
'';
};
vpn-container.config.systemd.services.pia-vpn-wireguard = {
description = "Initializes the PIA VPN WireGuard Tunnel";
wants = [ "network-online.target" ];
after = [ "network.target" "network-online.target" ];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ wireguard-tools iproute2 curl jq iptables ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
# pseudo calls wg-quick
# Near equivalent of "wg-quick up /tmp/${cfg.interfaceName}.conf"
# cannot actually call wg-quick because the interface has to be already
# created before the container taken ownership of the interface
# Thus, assumes wg interface was already created:
# ip link add ${cfg.interfaceName} type wireguard
${getChosenWireguardServer}
myaddress=`cat /tmp/${cfg.interfaceName}-address.conf`
wg setconf ${cfg.interfaceName} /tmp/${cfg.interfaceName}.conf
ip -4 address add $myaddress dev ${cfg.interfaceName}
ip link set mtu 1420 up dev ${cfg.interfaceName}
wg set ${cfg.interfaceName} fwmark ${toString cfg.wireguardListenPort}
ip -4 route add 0.0.0.0/0 dev ${cfg.interfaceName} table ${toString cfg.wireguardListenPort}
# TODO is this needed?
ip -4 rule add not fwmark ${toString cfg.wireguardListenPort} table ${toString cfg.wireguardListenPort}
ip -4 rule add table main suppress_prefixlength 0
# The rest of the script is only for only for port forwarding skip if not needed
if [ ${boolToString portForwarding} == false ]; then exit 0; fi
# Reserve port
${getPIAToken}
payload_and_signature=`curl -s -m 5 --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" -G --data-urlencode "token=$PIA_TOKEN" "https://$WG_HOSTNAME:19999/getSignature"`
signature=$(echo "$payload_and_signature" | jq -r '.signature')
payload=$(echo "$payload_and_signature" | jq -r '.payload')
port=$(echo "$payload" | base64 -d | jq -r '.port')
# Check if the port is acceptable
notallowed=(${concatStringsSep " " (map toString cfg.badPortForwardPorts)})
if [[ " ''${notallowed[*]} " =~ " $port " ]]; then
# the port PIA assigned is not allowed, kill the connection
wg-quick down /tmp/${cfg.interfaceName}.conf
exit 1
fi
# write reserved port to file readable for all users
echo $port > /tmp/${cfg.interfaceName}-port
chmod 644 /tmp/${cfg.interfaceName}-port
# write payload and signature info needed to allow refreshing allocated forwarded port
rm -f /tmp/${cfg.interfaceName}-port-renewal
touch /tmp/${cfg.interfaceName}-port-renewal
chmod 700 /tmp/${cfg.interfaceName}-port-renewal
echo $signature >> /tmp/${cfg.interfaceName}-port-renewal
echo $payload >> /tmp/${cfg.interfaceName}-port-renewal
# Block all traffic from VPN interface except for traffic that is from the forwarded port
iptables -I nixos-fw -p tcp --dport $port -j nixos-fw-accept -i ${cfg.interfaceName}
iptables -I nixos-fw -p udp --dport $port -j nixos-fw-accept -i ${cfg.interfaceName}
# The first port refresh triggers the port to be actually allocated
${refreshPIAPort}
${optionalString (cfg.forwardedPort != null) ''
# redirect the fowarded port
iptables -A INPUT -i ${cfg.interfaceName} -p tcp --dport $port -j ACCEPT
iptables -A INPUT -i ${cfg.interfaceName} -p udp --dport $port -j ACCEPT
iptables -A INPUT -i ${cfg.interfaceName} -p tcp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -A INPUT -i ${cfg.interfaceName} -p udp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -A PREROUTING -t nat -i ${cfg.interfaceName} -p tcp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
iptables -A PREROUTING -t nat -i ${cfg.interfaceName} -p udp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
''}
${optionalString cfg.forwardPortForTransmission ''
# assumes no auth needed for transmission
curlout=$(curl localhost:9091/transmission/rpc 2>/dev/null)
regex='X-Transmission-Session-Id\: (\w*)'
if [[ $curlout =~ $regex ]]; then
sessionId=''${BASH_REMATCH[1]}
else
exit 1
fi
# set the port in transmission
data='{"method": "session-set", "arguments": { "peer-port" :'$port' } }'
curl http://localhost:9091/transmission/rpc -d "$data" -H "X-Transmission-Session-Id: $sessionId"
''}
'';
preStop = ''
wg-quick down /tmp/${cfg.interfaceName}.conf
# The rest of the script is only for only for port forwarding skip if not needed
if [ ${boolToString portForwarding} == false ]; then exit 0; fi
${optionalString (cfg.forwardedPort != null) ''
# stop redirecting the forwarded port
iptables -D INPUT -i ${cfg.interfaceName} -p tcp --dport $port -j ACCEPT
iptables -D INPUT -i ${cfg.interfaceName} -p udp --dport $port -j ACCEPT
iptables -D INPUT -i ${cfg.interfaceName} -p tcp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -D INPUT -i ${cfg.interfaceName} -p udp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -D PREROUTING -t nat -i ${cfg.interfaceName} -p tcp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
iptables -D PREROUTING -t nat -i ${cfg.interfaceName} -p udp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
''}
'';
};
vpn-container.config.systemd.services.pia-vpn-wireguard-forward-port = {
enable = portForwarding;
description = "PIA VPN WireGuard Tunnel Port Forwarding";
after = [ "pia-vpn-wireguard.service" ];
requires = [ "pia-vpn-wireguard.service" ];
path = with pkgs; [ curl ];
serviceConfig = {
Type = "oneshot";
};
script = refreshPIAPort;
};
vpn-container.config.systemd.timers.pia-vpn-wireguard-forward-port = {
enable = portForwarding;
partOf = [ "pia-vpn-wireguard-forward-port.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*:0/10"; # 10 minutes
RandomizedDelaySec = "1m"; # vary by 1 min to give PIA servers some relief
};
};
age.secrets."pia-login.conf".file = ../../secrets/pia-login.age;
};
}

View File

@ -1,59 +0,0 @@
{ config, pkgs, lib, ... }:
# keeps peer to peer connections alive with a periodic ping
with lib;
with builtins;
# todo auto restart
let
cfg = config.keepalive-ping;
serviceTemplate = host:
{
"keepalive-ping@${host}" = {
description = "Periodic ping keep alive for ${host} connection";
requires = [ "network-online.target" ];
after = [ "network.target" "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig.Restart = "always";
path = with pkgs; [ iputils ];
script = ''
ping -i ${cfg.delay} ${host} &>/dev/null
'';
};
};
combineAttrs = foldl recursiveUpdate { };
serviceList = map serviceTemplate cfg.hosts;
services = combineAttrs serviceList;
in
{
options.keepalive-ping = {
enable = mkEnableOption "Enable keep alive ping task";
hosts = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Hosts to ping periodically
'';
};
delay = mkOption {
type = types.str;
default = "60";
description = ''
Ping interval in seconds of periodic ping per host being pinged
'';
};
};
config = mkIf cfg.enable {
systemd.services = services;
};
}

View File

@ -1,20 +0,0 @@
{ config, lib, ... }:
with lib;
let
cfg = config.services.tailscale;
in
{
options.services.tailscale.exitNode = mkEnableOption "Enable exit node support";
config.services.tailscale.enable = mkDefault (!config.boot.isContainer);
# MagicDNS
config.networking.nameservers = mkIf cfg.enable [ "1.1.1.1" "8.8.8.8" ];
config.networking.search = mkIf cfg.enable [ "koi-bebop.ts.net" ];
# exit node
config.networking.firewall.checkReversePath = mkIf cfg.exitNode "loose";
config.networking.ip_forward = mkIf cfg.exitNode true;
}

View File

@ -1,106 +0,0 @@
{ config, pkgs, lib, allModules, ... }:
with lib;
let
cfg = config.vpn-container;
in
{
options.vpn-container = {
enable = mkEnableOption "Enable VPN container";
containerName = mkOption {
type = types.str;
default = "vpn";
description = ''
Name of the VPN container.
'';
};
mounts = mkOption {
type = types.listOf types.str;
default = [ "/var/lib" ];
example = "/home/example";
description = ''
List of mounts on the host to bind to the vpn container.
'';
};
useOpenVPN = mkEnableOption "Uses OpenVPN instead of wireguard for PIA VPN connection";
config = mkOption {
type = types.anything;
default = { };
example = ''
{
services.nginx.enable = true;
}
'';
description = ''
NixOS config for the vpn container.
'';
};
};
config = mkIf cfg.enable {
pia.wireguard.enable = !cfg.useOpenVPN;
pia.wireguard.forwardPortForTransmission = !cfg.useOpenVPN;
containers.${cfg.containerName} = {
ephemeral = true;
autoStart = true;
bindMounts = mkMerge ([{
"/run/agenix" = {
hostPath = "/run/agenix";
isReadOnly = true;
};
}] ++ (lists.forEach cfg.mounts (mount:
{
"${mount}" = {
hostPath = mount;
isReadOnly = false;
};
}
)));
enableTun = cfg.useOpenVPN;
privateNetwork = true;
hostAddress = "172.16.100.1";
localAddress = "172.16.100.2";
config = {
imports = allModules ++ [ cfg.config ];
# networking.firewall.enable = mkForce false;
networking.firewall.trustedInterfaces = [
# completely trust internal interface to host
"eth0"
];
pia.openvpn.enable = cfg.useOpenVPN;
pia.openvpn.server = "swiss.privacy.network"; # swiss vpn
# TODO fix so it does run it's own resolver again
# run it's own DNS resolver
networking.useHostResolvConf = false;
# services.resolved.enable = true;
networking.nameservers = [ "1.1.1.1" "8.8.8.8" ];
};
};
# load secrets the container needs
age.secrets = config.containers.${cfg.containerName}.config.age.secrets;
# forwarding for vpn container (only for OpenVPN)
networking.nat.enable = mkIf cfg.useOpenVPN true;
networking.nat.internalInterfaces = mkIf cfg.useOpenVPN [
"ve-${cfg.containerName}"
];
networking.ip_forward = mkIf cfg.useOpenVPN true;
# assumes only one potential interface
networking.usePredictableInterfaceNames = false;
networking.nat.externalInterface = "eth0";
};
}

View File

@ -1,187 +0,0 @@
#!/usr/bin/env bash
set -eEo pipefail
# $@ := ""
set_route_vars() {
local network_var
local -a network_vars; read -ra network_vars <<<"${!route_network_*}"
for network_var in "${network_vars[@]}"; do
local -i i="${network_var#route_network_}"
local -a vars=("route_network_$i" "route_netmask_$i" "route_gateway_$i" "route_metric_$i")
route_networks[i]="${!vars[0]}"
route_netmasks[i]="${!vars[1]:-255.255.255.255}"
route_gateways[i]="${!vars[2]:-$route_vpn_gateway}"
route_metrics[i]="${!vars[3]:-0}"
done
}
# Configuration.
readonly prog="$(basename "$0")"
readonly private_nets="127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
declare -a remotes cnf_remote_domains cnf_remote_ips route_networks route_netmasks route_gateways route_metrics
read -ra remotes <<<"$(env|grep -oP '^remote_[0-9]+=.*'|sort -n|cut -d= -f2|tr '\n' '\t')"
read -ra cnf_remote_domains <<<"$(printf '%s\n' "${remotes[@]%%*[0-9]}"|sort -u|tr '\n' '\t')"
read -ra cnf_remote_ips <<<"$(printf '%s\n' "${remotes[@]##*[!0-9.]*}"|sort -u|tr '\n' '\t')"
set_route_vars
read -ra numbered_vars <<<"${!foreign_option_*} ${!proto_*} ${!remote_*} ${!remote_port_*} \
${!route_network_*} ${!route_netmask_*} ${!route_gateway_*} ${!route_metric_*}"
readonly numbered_vars "${numbered_vars[@]}" dev ifconfig_local ifconfig_netmask ifconfig_remote \
route_net_gateway route_vpn_gateway script_type trusted_ip trusted_port untrusted_ip untrusted_port \
remotes cnf_remote_domains cnf_remote_ips route_networks route_netmasks route_gateways route_metrics
readonly cur_remote_ip="${trusted_ip:-$untrusted_ip}"
readonly cur_port="${trusted_port:-$untrusted_port}"
# $@ := ""
update_hosts() {
if remote_entries="$(getent -s dns hosts "${cnf_remote_domains[@]}"|grep -v :)"; then
local -r beg="# VPNFAILSAFE BEGIN" end="# VPNFAILSAFE END"
{
sed -e "/^$beg/,/^$end/d" /etc/hosts
echo -e "$beg\\n$remote_entries\\n$end"
} >/etc/hosts.vpnfailsafe
chmod --reference=/etc/hosts /etc/hosts.vpnfailsafe
mv /etc/hosts.vpnfailsafe /etc/hosts
fi
}
# $@ := "up" | "down"
update_routes() {
local -a resolved_ips
read -ra resolved_ips <<<"$(getent -s files hosts "${cnf_remote_domains[@]:-ENOENT}"|cut -d' ' -f1|tr '\n' '\t' || true)"
local -ar remote_ips=("$cur_remote_ip" "${resolved_ips[@]}" "${cnf_remote_ips[@]}")
if [[ "$*" == up ]]; then
for remote_ip in "${remote_ips[@]}"; do
if [[ -n "$remote_ip" && -z "$(ip route show "$remote_ip")" ]]; then
ip route add "$remote_ip" via "$route_net_gateway"
fi
done
for net in 0.0.0.0/1 128.0.0.0/1; do
if [[ -z "$(ip route show "$net")" ]]; then
ip route add "$net" via "$route_vpn_gateway"
fi
done
for i in $(seq 1 "${#route_networks[@]}"); do
if [[ -z "$(ip route show "${route_networks[i]}/${route_netmasks[i]}")" ]]; then
ip route add "${route_networks[i]}/${route_netmasks[i]}" \
via "${route_gateways[i]}" metric "${route_metrics[i]}" dev "$dev"
fi
done
elif [[ "$*" == down ]]; then
for route in "${remote_ips[@]}" 0.0.0.0/1 128.0.0.0/1; do
if [[ -n "$route" && -n "$(ip route show "$route")" ]]; then
ip route del "$route"
fi
done
for i in $(seq 1 "${#route_networks[@]}"); do
if [[ -n "$(ip route show "${route_networks[i]}/${route_netmasks[i]}")" ]]; then
ip route del "${route_networks[i]}/${route_netmasks[i]}"
fi
done
fi
}
# $@ := ""
update_firewall() {
# $@ := "INPUT" | "OUTPUT" | "FORWARD"
insert_chain() {
if iptables -C "$*" -j "VPNFAILSAFE_$*" 2>/dev/null; then
iptables -D "$*" -j "VPNFAILSAFE_$*"
for opt in F X; do
iptables -"$opt" "VPNFAILSAFE_$*"
done
fi
iptables -N "VPNFAILSAFE_$*"
iptables -I "$*" -j "VPNFAILSAFE_$*"
}
# $@ := "INPUT" | "OUTPUT"
accept_remotes() {
case "$@" in
INPUT) local -r icmp_type=reply io=i sd=s states="";;
OUTPUT) local -r icmp_type=request io=o sd=d states=NEW,;;
esac
local -r public_nic="$(ip route show "$cur_remote_ip"|cut -d' ' -f5)"
local -ar suf=(-m conntrack --ctstate "$states"RELATED,ESTABLISHED -"$io" "${public_nic:?}" -j ACCEPT)
icmp_rule() {
iptables "$1" "$2" -p icmp --icmp-type "echo-$icmp_type" -"$sd" "$3" "${suf[@]/%ACCEPT/RETURN}"
}
for ((i=1; i <= ${#remotes[*]}; ++i)); do
local port="remote_port_$i"
local proto="proto_$i"
iptables -A "VPNFAILSAFE_$*" -p "${!proto%-client}" -"$sd" "${remotes[i-1]}" --"$sd"port "${!port}" "${suf[@]}"
if ! icmp_rule -C "VPNFAILSAFE_$*" "${remotes[i-1]}" 2>/dev/null; then
icmp_rule -A "VPNFAILSAFE_$*" "${remotes[i-1]}"
fi
done
if ! iptables -S|grep -q "^-A VPNFAILSAFE_$* .*-$sd $cur_remote_ip/32 .*-j ACCEPT$"; then
for p in tcp udp; do
iptables -A "VPNFAILSAFE_$*" -p "$p" -"$sd" "$cur_remote_ip" --"$sd"port "${cur_port}" "${suf[@]}"
done
icmp_rule -A "VPNFAILSAFE_$*" "$cur_remote_ip"
fi
}
# $@ := "OUTPUT" | "FORWARD"
reject_dns() {
for proto in udp tcp; do
iptables -A "VPNFAILSAFE_$*" -p "$proto" --dport 53 ! -o "$dev" -j REJECT
done
}
# $@ := "INPUT" | "OUTPUT" | "FORWARD"
pass_private_nets() {
case "$@" in
INPUT) local -r io=i sd=s;;&
OUTPUT|FORWARD) local -r io=o sd=d;;&
INPUT) local -r vpn="${ifconfig_remote:-$ifconfig_local}/${ifconfig_netmask:-32}"
iptables -A "VPNFAILSAFE_$*" -"$sd" "$vpn" -"$io" "$dev" -j RETURN
for i in $(seq 1 "${#route_networks[@]}"); do
iptables -A "VPNFAILSAFE_$*" -"$sd" "${route_networks[i]}/${route_netmasks[i]}" -"$io" "$dev" -j RETURN
done;;&
*) iptables -A "VPNFAILSAFE_$*" -"$sd" "$private_nets" ! -"$io" "$dev" -j RETURN;;&
INPUT) iptables -A "VPNFAILSAFE_$*" -s "$private_nets" -i "$dev" -j DROP;;&
*) for iface in "$dev" lo+; do
iptables -A "VPNFAILSAFE_$*" -"$io" "$iface" -j RETURN
done;;
esac
}
# $@ := "INPUT" | "OUTPUT" | "FORWARD"
drop_other() {
iptables -A "VPNFAILSAFE_$*" -j DROP
}
for chain in INPUT OUTPUT FORWARD; do
insert_chain "$chain"
[[ $chain == FORWARD ]] || accept_remotes "$chain"
[[ $chain == INPUT ]] || reject_dns "$chain"
pass_private_nets "$chain"
drop_other "$chain"
done
}
# $@ := ""
cleanup() {
update_resolv down
update_routes down
}
trap cleanup INT TERM
# $@ := line_number exit_code
err_msg() {
echo "$0:$1: \`$(sed -n "$1,+0{s/^\\s*//;p}" "$0")' returned $2" >&2
cleanup
}
trap 'err_msg "$LINENO" "$?"' ERR
# $@ := ""
main() {
case "${script_type:-down}" in
up) for f in hosts routes firewall; do "update_$f" up; done;;
down) update_routes down
update_resolv down;;
esac
}
main

View File

@ -1,60 +0,0 @@
{ config, lib, ... }:
let
builderRole = "nix-builder";
builderUserName = "nix-builder";
machinesByRole = role: lib.filterAttrs (hostname: cfg: builtins.elem role cfg.systemRoles) config.machines.hosts;
otherMachinesByRole = role: lib.filterAttrs (hostname: cfg: hostname != config.networking.hostName) (machinesByRole role);
thisMachineHasRole = role: builtins.hasAttr config.networking.hostName (machinesByRole role);
builders = machinesByRole builderRole;
thisMachineIsABuilder = thisMachineHasRole builderRole;
# builders don't include themselves as a remote builder
otherBuilders = lib.filterAttrs (hostname: cfg: hostname != config.networking.hostName) builders;
in
lib.mkMerge [
# configure builder
(lib.mkIf thisMachineIsABuilder {
users.users.${builderUserName} = {
description = "Distributed Nix Build User";
group = builderUserName;
isSystemUser = true;
createHome = true;
home = "/var/lib/nix-builder";
useDefaultShell = true;
openssh.authorizedKeys.keys = builtins.map
(builderCfg: builderCfg.hostKey)
(builtins.attrValues config.machines.hosts);
};
users.groups.${builderUserName} = { };
nix.settings.trusted-users = [
builderUserName
];
})
# use each builder
{
nix.distributedBuilds = true;
nix.buildMachines = builtins.map
(builderCfg: {
hostName = builtins.elemAt builderCfg.hostNames 0;
system = builderCfg.arch;
protocol = "ssh-ng";
sshUser = builderUserName;
sshKey = "/etc/ssh/ssh_host_ed25519_key";
maxJobs = 3;
speedFactor = 10;
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
})
(builtins.attrValues otherBuilders);
# It is very likely that the builder's internet is faster or just as fast
nix.extraOptions = ''
builders-use-substitutes = true
'';
}
]

View File

@ -2,32 +2,28 @@
let
cfg = config.de;
in
{
in {
config = lib.mkIf cfg.enable {
# Audio
sound.enable = true;
# enable pulseaudio support for packages
nixpkgs.config.pulseaudio = true;
# realtime audio
# realtime pulseaudio
security.rtkit.enable = true;
services.pipewire = {
hardware.pulseaudio = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
};
support32Bit = true;
package = pkgs.pulseaudioFull; # bt headset support
services.pipewire.extraConfig.pipewire."92-fix-wine-audio" = {
context.properties = {
default.clock.rate = 48000;
default.clock.quantum = 256;
default.clock.min-quantum = 256;
default.clock.max-quantum = 2048;
};
# TODO: switch on connect isn't working for some reason (at least when in kde)
extraConfig = "
load-module module-switch-on-connect
load-module module-switch-on-connect ignore_virtual=no
";
};
users.users.googlebot.extraGroups = [ "audio" ];
# bt headset support

View File

@ -2,23 +2,7 @@
let
cfg = config.de;
nv-codec-headers-11-1-5-1 = pkgs.stdenv.mkDerivation rec {
pname = "nv-codec-headers";
version = "11.1.5.1";
src = pkgs.fetchgit {
url = "https://git.videolan.org/git/ffmpeg/nv-codec-headers.git";
rev = "n${version}";
sha256 = "yTOKLjyYLxT/nI1FBOMwHpkDhfuua3+6Z5Mpb7ZrRhU=";
};
makeFlags = [
"PREFIX=$(out)"
];
};
in
{
in {
config = lib.mkIf cfg.enable {
# chromium with specific extensions + settings
programs.chromium = {
@ -29,9 +13,6 @@ in
"oboonakemofpalcgghocfoadofidjkkk" # keepassxc plugin
"cimiefiiaegbelhefglklhhakcgmhkai" # plasma integration
"hkgfoiooedgoejojocmhlaklaeopbecg" # picture in picture
"mnjggcdmjocbbbhaepdhchncahnbgone" # SponsorBlock
"dhdgffkkebhmkfjojejmpbldmpobfkfo" # Tampermonkey
# "ehpdicggenhgapiikfpnmppdonadlnmp" # Disable Scroll Jacking
];
extraOpts = {
"BrowserSignin" = 0;
@ -41,30 +22,24 @@ in
"SpellcheckLanguage" = [ "en-US" ];
};
defaultSearchProviderSuggestURL = null;
defaultSearchProviderSearchURL = "https://duckduckgo.com/?q={searchTerms}&kp=-1&kl=us-en";
defaultSearchProviderSearchURL = " https://duckduckgo.com/?q={searchTerms}&kp=-1&kl=us-en";
};
# hardware accelerated video playback (on intel)
nixpkgs.config.packageOverrides = pkgs: {
vaapiIntel = pkgs.vaapiIntel.override { enableHybridCodec = true; };
chromium = pkgs.chromium.override {
gnomeKeyringSupport = true;
enableWideVine = true;
# ungoogled = true;
# --enable-native-gpu-memory-buffers # fails on AMD APU
# --enable-webrtc-vp9-support
commandLineArgs = "--use-vulkan";
};
};
# todo vulkan in chrome
# todo video encoding in chrome
hardware.graphics = {
hardware.opengl = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver # LIBVA_DRIVER_NAME=iHD
vaapiIntel # LIBVA_DRIVER_NAME=i965 (older but works better for Firefox/Chromium)
# vaapiVdpau
vaapiIntel # LIBVA_DRIVER_NAME=i965 (older but works better for Firefox/Chromium)
vaapiVdpau
libvdpau-va-gl
nvidia-vaapi-driver
];
extraPackages32 = with pkgs.pkgsi686Linux; [ vaapiIntel ];
};

47
common/pc/de.nix Normal file
View File

@ -0,0 +1,47 @@
{ config, pkgs, lib, ... }:
let
cfg = config.de;
in {
imports = [
./kde.nix
./xfce.nix
./yubikey.nix
./chromium.nix
./firefox.nix
./audio.nix
# ./torbrowser.nix
./pithos.nix
./vscodium.nix
./discord.nix
./steam.nix
./touchpad.nix
];
options.de = {
enable = lib.mkEnableOption "enable desktop environment";
};
config = lib.mkIf cfg.enable {
# vulkan
hardware.opengl.driSupport = true;
hardware.opengl.driSupport32Bit = true;
# Applications
users.users.googlebot.packages = with pkgs; [
chromium keepassxc mumble tigervnc bluez-tools vscodium element-desktop mpv
libva-utils ffmpeg-full
];
# Networking
networking.networkmanager.enable = true;
users.users.googlebot.extraGroups = [ "networkmanager" ];
# Printing
services.printing.enable = true;
# Security
services.gnome.gnome-keyring.enable = true;
security.pam.services.googlebot.enableGnomeKeyring = true;
};
}

View File

@ -1,96 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.de;
in
{
imports = [
./kde.nix
./yubikey.nix
./chromium.nix
./firefox.nix
./audio.nix
./pithos.nix
./vscodium.nix
./discord.nix
./steam.nix
./touchpad.nix
./mount-samba.nix
./udev.nix
./virtualisation.nix
];
options.de = {
enable = lib.mkEnableOption "enable desktop environment";
};
config = lib.mkIf cfg.enable {
environment.systemPackages = with pkgs; [
# https://github.com/NixOS/nixpkgs/pull/328086#issuecomment-2235384618
gparted
];
# Applications
users.users.googlebot.packages = with pkgs; [
chromium
keepassxc
mumble
tigervnc
bluez-tools
element-desktop
mpv
nextcloud-client
signal-desktop
libreoffice-fresh
thunderbird
spotify
arduino
yt-dlp
jellyfin-media-player
joplin-desktop
config.inputs.deploy-rs.packages.${config.currentSystem}.deploy-rs
lxqt.pavucontrol-qt
barrier
# For Nix IDE
nixpkgs-fmt
nixd
nil
];
# Networking
networking.networkmanager.enable = true;
users.users.googlebot.extraGroups = [ "networkmanager" ];
# Printing
services.printing.enable = true;
services.printing.drivers = with pkgs; [
gutenprint
];
# Printer discovery
services.avahi.enable = true;
services.avahi.nssmdns4 = true;
programs.file-roller.enable = true;
# Security
services.gnome.gnome-keyring.enable = true;
security.pam.services.googlebot.enableGnomeKeyring = true;
# Android dev
programs.adb.enable = true;
# Mount personal SMB stores
services.mount-samba.enable = true;
# allow building ARM derivations
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
# for luks onlock over tor
services.tor.enable = true;
services.tor.client.enable = true;
# Enable wayland support in various chromium based applications
environment.sessionVariables.NIXOS_OZONE_WL = "1";
};
}

View File

@ -2,11 +2,10 @@
let
cfg = config.de;
in
{
in {
config = lib.mkIf cfg.enable {
users.users.googlebot.packages = [
pkgs.discord
];
};
}
}

View File

@ -20,6 +20,31 @@ let
};
firefox = pkgs.wrapFirefox somewhatPrivateFF {
desktopName = "Sneed Browser";
nixExtensions = [
(pkgs.fetchFirefoxAddon {
name = "ublock-origin";
url = "https://addons.mozilla.org/firefox/downloads/file/3719054/ublock_origin-1.33.2-an+fx.xpi";
sha256 = "XDpe9vW1R1iVBTI4AmNgAg1nk7BVQdIAMuqd0cnK5FE=";
})
(pkgs.fetchFirefoxAddon {
name = "sponsorblock";
url = "https://addons.mozilla.org/firefox/downloads/file/3720594/sponsorblock_skip_sponsorships_on_youtube-2.0.12.3-an+fx.xpi";
sha256 = "HRtnmZWyXN3MKo4AvSYgNJGkBEsa2RaMamFbkz+YzQg=";
})
(pkgs.fetchFirefoxAddon {
name = "KeePassXC-Browser";
url = "https://addons.mozilla.org/firefox/downloads/file/3720664/keepassxc_browser-1.7.6-fx.xpi";
sha256 = "3K404/eq3amHhIT0WhzQtC892he5I0kp2SvbzE9dbZg=";
})
(pkgs.fetchFirefoxAddon {
name = "https-everywhere";
url = "https://addons.mozilla.org/firefox/downloads/file/3716461/https_everywhere-2021.1.27-an+fx.xpi";
sha256 = "2gSXSLunKCwPjAq4Wsj0lOeV551r3G+fcm1oeqjMKh8=";
})
];
extraPolicies = {
CaptivePortal = false;
DisableFirefoxStudies = true;
@ -46,8 +71,14 @@ let
TopSites = false;
};
UserMessaging = {
ExtensionRecommendations = false;
SkipOnboarding = true;
ExtensionRecommendations = false;
SkipOnboarding = true;
};
WebsiteFilter = {
Block = [
"http://paradigminteractive.io/"
"https://paradigminteractive.io/"
];
};
};
@ -61,4 +92,4 @@ in
config = lib.mkIf cfg.enable {
users.users.googlebot.packages = [ firefox ];
};
}
}

View File

@ -2,19 +2,19 @@
let
cfg = config.de;
in
{
in {
config = lib.mkIf cfg.enable {
services.displayManager.sddm.enable = true;
services.displayManager.sddm.wayland.enable = true;
services.desktopManager.plasma6.enable = true;
# kde plasma
services.xserver = {
enable = true;
desktopManager.plasma5.enable = true;
displayManager.sddm.enable = true;
};
# kde apps
nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
users.users.googlebot.packages = with pkgs; [
# akonadi
# kmail
# plasma5Packages.kmail-account-wizard
kdePackages.kate
akonadi kmail plasma5Packages.kmail-account-wizard
];
};
};
}

View File

@ -1,50 +0,0 @@
# mounts the samba share on s0 over tailscale
{ config, lib, pkgs, ... }:
let
cfg = config.services.mount-samba;
# prevents hanging on network split and other similar niceties to ensure a stable connection
network_opts = "nostrictsync,cache=strict,handlecache,handletimeout=30000,rwpidforward,mapposix,soft,resilienthandles,echo_interval=10,noblocksend,fsc";
systemd_opts = "x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
user_opts = "uid=${toString config.users.users.googlebot.uid},file_mode=0660,dir_mode=0770,user";
auth_opts = "sec=ntlmv2i,credentials=/run/agenix/smb-secrets";
version_opts = "vers=3.1.1";
public_user_opts = "gid=${toString config.users.groups.users.gid}";
opts = "${systemd_opts},${network_opts},${user_opts},${version_opts},${auth_opts}";
in
{
options.services.mount-samba = {
enable = lib.mkEnableOption "enable mounting samba shares";
};
config = lib.mkIf (cfg.enable && config.services.tailscale.enable) {
fileSystems."/mnt/public" = {
device = "//s0.koi-bebop.ts.net/public";
fsType = "cifs";
options = [ "${opts},${public_user_opts}" ];
};
fileSystems."/mnt/private" = {
device = "//s0.koi-bebop.ts.net/googlebot";
fsType = "cifs";
options = [ opts ];
};
age.secrets.smb-secrets.file = ../../secrets/smb-secrets.age;
environment.shellAliases = {
# remount storage
remount_public = "sudo systemctl restart mnt-public.mount";
remount_private = "sudo systemctl restart mnt-private.mount";
# Encrypted Vault
vault_unlock = "${pkgs.gocryptfs}/bin/gocryptfs /mnt/private/.vault/ /mnt/vault/";
vault_lock = "umount /mnt/vault/";
};
};
}

View File

@ -2,8 +2,7 @@
let
cfg = config.de;
in
{
in {
config = lib.mkIf cfg.enable {
nixpkgs.overlays = [
(self: super: {
@ -12,7 +11,7 @@ in
version = "1.5.1";
src = super.fetchFromGitHub {
owner = pname;
repo = pname;
repo = pname;
rev = version;
sha256 = "il7OAALpHFZ6wjco9Asp04zWHCD8Ni+iBdiJWcMiQA4=";
};

View File

@ -2,8 +2,7 @@
let
cfg = config.de;
in
{
in {
config = lib.mkIf cfg.enable {
programs.steam.enable = true;
hardware.steam-hardware.enable = true; # steam controller
@ -12,4 +11,4 @@ in
pkgs.steam
];
};
}
}

24
common/pc/torbrowser.nix Normal file
View File

@ -0,0 +1,24 @@
{ lib, config, pkgs, ... }:
let
cfg = config.de;
in {
config = lib.mkIf cfg.enable {
nixpkgs.overlays = [
(self: super: {
tor-browser-bundle-bin = super.tor-browser-bundle-bin.overrideAttrs (old: rec {
version = "10.0.10";
lang = "en-US";
src = pkgs.fetchurl {
url = "https://dist.torproject.org/torbrowser/${version}/tor-browser-linux64-${version}_${lang}.tar.xz";
sha256 = "vYWZ+NsGN8YH5O61+zrUjlFv3rieaBqjBQ+a18sQcZg=";
};
});
})
];
users.users.googlebot.packages = with pkgs; [
tor-browser-bundle-bin
];
};
}

View File

@ -1,11 +1,14 @@
{ lib, config, pkgs, ... }:
let
cfg = config.de;
in
{
cfg = config.de.touchpad;
in {
options.de.touchpad = {
enable = lib.mkEnableOption "enable touchpad";
};
config = lib.mkIf cfg.enable {
services.libinput.enable = true;
services.libinput.touchpad.naturalScrolling = true;
services.xserver.libinput.enable = true;
services.xserver.libinput.touchpad.naturalScrolling = true;
};
}

View File

@ -1,25 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.de;
in
{
config = lib.mkIf cfg.enable {
services.udev.extraRules = ''
# depthai
SUBSYSTEM=="usb", ATTRS{idVendor}=="03e7", MODE="0666"
# Moonlander
# Rules for Oryx web flashing and live training
KERNEL=="hidraw*", ATTRS{idVendor}=="16c0", MODE="0664", GROUP="plugdev"
KERNEL=="hidraw*", ATTRS{idVendor}=="3297", MODE="0664", GROUP="plugdev"
# Wally Flashing rules for the Moonlander and Planck EZ
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE:="0666", SYMLINK+="stm32_dfu"
'';
services.udev.packages = [ pkgs.platformio ];
users.groups.plugdev = {
members = [ "googlebot" ];
};
};
}

View File

@ -1,23 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.de;
in
{
config = lib.mkIf cfg.enable {
# AppVMs
virtualisation.appvm.enable = true;
virtualisation.appvm.user = "googlebot";
# Use podman instead of docker
virtualisation.podman.enable = true;
virtualisation.podman.dockerCompat = true;
# virt-manager
virtualisation.libvirtd.enable = true;
programs.dconf.enable = true;
virtualisation.spiceUSBRedirection.enable = true;
environment.systemPackages = with pkgs; [ virt-manager ];
users.users.googlebot.extraGroups = [ "libvirtd" "adbusers" ];
};
}

View File

@ -4,35 +4,8 @@ let
cfg = config.de;
extensions = with pkgs.vscode-extensions; [
bbenoist.nix # nix syntax support
arrterian.nix-env-selector # nix dev envs
dart-code.dart-code
dart-code.flutter
golang.go
jnoortheen.nix-ide
ms-vscode.cpptools
rust-lang.rust-analyzer
vadimcn.vscode-lldb
tauri-apps.tauri-vscode
] ++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
{
name = "platformio-ide";
publisher = "platformio";
version = "3.1.1";
sha256 = "g9yTG3DjVUS2w9eHGAai5LoIfEGus+FPhqDnCi4e90Q=";
}
{
name = "wgsl-analyzer";
publisher = "wgsl-analyzer";
version = "0.8.1";
sha256 = "ckclcxdUxhjWlPnDFVleLCWgWxUEENe0V328cjaZv+Y=";
}
{
name = "volar";
publisher = "Vue";
version = "2.2.4";
sha256 = "FHS/LNjSUVfCb4SVF9naR4W0JqycWzSWiK54jfbRagA=";
}
bbenoist.Nix # nix syntax support
# arrterian.nix-env-selector # nix dev envs
];
vscodium-with-extensions = pkgs.vscode-with-extensions.override {

20
common/pc/xfce.nix Normal file
View File

@ -0,0 +1,20 @@
{ lib, config, pkgs, ... }:
let
cfg = config.de;
in {
config = lib.mkIf cfg.enable {
services.xserver = {
enable = true;
desktopManager = {
xterm.enable = false;
xfce.enable = true;
};
displayManager.sddm.enable = true;
};
# xfce apps
users.users.googlebot.packages = with pkgs; [
];
};
}

View File

@ -2,8 +2,7 @@
let
cfg = config.de;
in
{
in {
config = lib.mkIf cfg.enable {
# yubikey
services.pcscd.enable = true;

97
common/pia.nix Normal file
View File

@ -0,0 +1,97 @@
{ config, pkgs, lib, ... }:
let
cfg = config.pia;
in
{
options.pia = {
enable = lib.mkEnableOption "Enable private internet access";
};
config = lib.mkIf cfg.enable {
services.openvpn = {
servers = {
us-east = {
config = ''
client
dev tun
proto udp
remote us-washingtondc.privacy.network 1198
resolv-retry infinite
nobind
persist-key
persist-tun
cipher aes-128-cbc
auth sha1
tls-client
remote-cert-tls server
auth-user-pass
compress
verb 1
reneg-sec 0
<crl-verify>
-----BEGIN X509 CRL-----
MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI
EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl
cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw
HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0
ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl
aW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZa
MCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG
9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5
jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EW
B4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Re
ze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA
5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqy
MR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A==
-----END X509 CRL-----
</crl-verify>
<ca>
-----BEGIN CERTIFICATE-----
MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV
BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu
dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx
IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB
FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1
MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg
QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE
AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50
ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy
bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD
L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX
lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp
cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/
8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB
/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC
OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL
y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO
sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM
b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G
A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg
SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz
czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j
b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn
a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU
ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3
7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC
GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz
1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt
YDQ8z9v+DMO6iwyIDRiU
-----END CERTIFICATE-----
</ca>
disable-occ
auth-user-pass /secret/pia-login.conf
'';
autoStart = true;
up = "echo nameserver $nameserver | ${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
down = "${pkgs.openresolv}/sbin/resolvconf -d $dev";
};
};
};
};
}

View File

@ -1,87 +0,0 @@
# Starting point:
# https://github.com/aldoborrero/mynixpkgs/commit/c501c1e32dba8f4462dcecb57eee4b9e52038e27
{ config, pkgs, lib, ... }:
let
cfg = config.services.actual-server;
stateDir = "/var/lib/${cfg.stateDirName}";
in
{
options.services.actual-server = {
enable = lib.mkEnableOption "Actual Server";
hostname = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "Hostname for the Actual Server.";
};
port = lib.mkOption {
type = lib.types.int;
default = 25448;
description = "Port on which the Actual Server should listen.";
};
stateDirName = lib.mkOption {
type = lib.types.str;
default = "actual-server";
description = "Name of the directory under /var/lib holding the server's data.";
};
upload = {
fileSizeSyncLimitMB = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "File size limit in MB for synchronized files.";
};
syncEncryptedFileSizeLimitMB = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "File size limit in MB for synchronized encrypted files.";
};
fileSizeLimitMB = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "File size limit in MB for file uploads.";
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.actual-server = {
description = "Actual Server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.actual-server}/bin/actual-server";
Restart = "always";
StateDirectory = cfg.stateDirName;
WorkingDirectory = stateDir;
DynamicUser = true;
UMask = "0007";
};
environment = {
NODE_ENV = "production";
ACTUAL_PORT = toString cfg.port;
# Actual is actually very bad at configuring it's own paths despite that information being readily available
ACTUAL_USER_FILES = "${stateDir}/user-files";
ACTUAL_SERVER_FILES = "${stateDir}/server-files";
ACTUAL_DATA_DIR = stateDir;
ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB = toString (cfg.upload.fileSizeSyncLimitMB or "");
ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SIZE_LIMIT_MB = toString (cfg.upload.syncEncryptedFileSizeLimitMB or "");
ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB = toString (cfg.upload.fileSizeLimitMB or "");
};
};
services.nginx.virtualHosts.${cfg.hostname} = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://localhost:${toString cfg.port}";
};
};
}

View File

@ -1,43 +0,0 @@
{ config, lib, ... }:
with lib;
let
cfg = config.ceph;
in
{
options.ceph = { };
config = mkIf cfg.enable {
# ceph.enable = true;
## S3 Object gateway
#ceph.rgw.enable = true;
#ceph.rgw.daemons = [
#];
# https://docs.ceph.com/en/latest/start/intro/
# meta object storage daemon
ceph.osd.enable = true;
ceph.osd.daemons = [
];
# monitor's ceph state
ceph.mon.enable = true;
ceph.mon.daemons = [
];
# manage ceph
ceph.mgr.enable = true;
ceph.mgr.daemons = [
];
# metadata server
ceph.mds.enable = true;
ceph.mds.daemons = [
];
ceph.global.fsid = "925773DC-D95F-476C-BBCD-08E01BF0865F";
};
}

View File

@ -1,25 +0,0 @@
{ config, pkgs, ... }:
{
imports = [
./nginx.nix
./thelounge.nix
./mumble.nix
./icecast.nix
./nginx-stream.nix
./matrix.nix
./zerobin.nix
./gitea.nix
./radio.nix
./samba.nix
./owncast.nix
./mailserver.nix
./nextcloud.nix
./iodine.nix
./searx.nix
./gitea-actions-runner.nix
./librechat.nix
./actualbudget.nix
./unifi.nix
];
}

View File

@ -1,136 +0,0 @@
{ config, pkgs, lib, allModules, ... }:
# Gitea Actions Runner. Starts 'host' runner that runs directly on the host inside of a nixos container
# This is useful for providing a real Nix/OS builder to gitea.
# Warning, NixOS containers are not secure. For example, the container shares the /nix/store
# Therefore, this should not be used to run untrusted code.
# To enable, assign a machine the 'gitea-actions-runner' system role
# TODO: skipping running inside of nixos container for now because of issues getting docker/podman running
let
runnerRole = "gitea-actions-runner";
runners = config.machines.roles.${runnerRole};
thisMachineIsARunner = builtins.elem config.networking.hostName runners;
containerName = "gitea-runner";
in
{
config = lib.mkIf (thisMachineIsARunner && !config.boot.isContainer) {
# containers.${containerName} = {
# ephemeral = true;
# autoStart = true;
# # for podman
# enableTun = true;
# # privateNetwork = true;
# # hostAddress = "172.16.101.1";
# # localAddress = "172.16.101.2";
# bindMounts =
# {
# "/run/agenix/gitea-actions-runner-token" = {
# hostPath = "/run/agenix/gitea-actions-runner-token";
# isReadOnly = true;
# };
# "/var/lib/gitea-runner" = {
# hostPath = "/var/lib/gitea-runner";
# isReadOnly = false;
# };
# };
# extraFlags = [
# # Allow podman
# ''--system-call-filter=thisystemcalldoesnotexistforsure''
# ];
# additionalCapabilities = [
# "CAP_SYS_ADMIN"
# ];
# config = {
# imports = allModules;
# # speeds up evaluation
# nixpkgs.pkgs = pkgs;
# networking.hostName = lib.mkForce containerName;
# # don't use remote builders
# nix.distributedBuilds = lib.mkForce false;
# environment.systemPackages = with pkgs; [
# git
# # Gitea Actions rely heavily on node. Include it because it would be installed anyway.
# nodejs
# ];
# services.gitea-actions-runner.instances.inst = {
# enable = true;
# name = config.networking.hostName;
# url = "https://git.neet.dev/";
# tokenFile = "/run/agenix/gitea-actions-runner-token";
# labels = [
# "ubuntu-latest:docker://node:18-bullseye"
# "nixos:host"
# ];
# };
# # To allow building on the host, must override the the service's config so it doesn't use a dynamic user
# systemd.services.gitea-runner-inst.serviceConfig.DynamicUser = lib.mkForce false;
# users.users.gitea-runner = {
# home = "/var/lib/gitea-runner";
# group = "gitea-runner";
# isSystemUser = true;
# createHome = true;
# };
# users.groups.gitea-runner = { };
# virtualisation.podman.enable = true;
# boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
# };
# };
# networking.nat.enable = true;
# networking.nat.internalInterfaces = [
# "ve-${containerName}"
# ];
# networking.ip_forward = true;
# don't use remote builders
nix.distributedBuilds = lib.mkForce false;
services.gitea-actions-runner.instances.inst = {
enable = true;
name = config.networking.hostName;
url = "https://git.neet.dev/";
tokenFile = "/run/agenix/gitea-actions-runner-token";
labels = [
"ubuntu-latest:docker://node:18-bullseye"
"nixos:host"
];
};
environment.systemPackages = with pkgs; [
git
# Gitea Actions rely heavily on node. Include it because it would be installed anyway.
nodejs
];
# To allow building on the host, must override the the service's config so it doesn't use a dynamic user
systemd.services.gitea-runner-inst.serviceConfig.DynamicUser = lib.mkForce false;
users.users.gitea-runner = {
home = "/var/lib/gitea-runner";
group = "gitea-runner";
isSystemUser = true;
createHome = true;
};
users.groups.gitea-runner = { };
virtualisation.podman.enable = true;
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
age.secrets.gitea-actions-runner-token.file = ../../secrets/gitea-actions-runner-token.age;
};
}

View File

@ -1,9 +1,8 @@
{ lib, pkgs, config, ... }:
{ lib, config, ... }:
let
cfg = config.services.gitea;
in
{
in {
options.services.gitea = {
hostname = lib.mkOption {
type = lib.types.str;
@ -12,64 +11,24 @@ in
};
config = lib.mkIf cfg.enable {
services.gitea = {
appName = cfg.hostname;
lfs.enable = true;
# dump.enable = true;
domain = cfg.hostname;
ssh.enable = true;
# lfs.enable = true;
dump.enable = true;
cookieSecure = true;
disableRegistration = true;
settings = {
server = {
ROOT_URL = "https://${cfg.hostname}/";
DOMAIN = cfg.hostname;
};
other = {
SHOW_FOOTER_VERSION = false;
};
ui = {
DEFAULT_THEME = "gitea-dark";
};
service = {
DISABLE_REGISTRATION = true;
};
session = {
COOKIE_SECURE = true;
PROVIDER = "db";
SESSION_LIFE_TIME = 259200; # 3 days
GC_INTERVAL_TIME = 259200; # 3 days
};
mailer = {
ENABLED = true;
MAILER_TYPE = "smtp";
SMTP_ADDR = "mail.neet.dev";
SMTP_PORT = "465";
IS_TLS_ENABLED = true;
USER = "robot@runyan.org";
FROM = "no-reply@neet.dev";
};
actions = {
ENABLED = true;
};
indexer = {
REPO_INDEXER_ENABLED = true;
};
};
mailerPasswordFile = "/run/agenix/robots-email-pw";
};
age.secrets.robots-email-pw = {
file = ../../secrets/robots-email-pw.age;
owner = config.services.gitea.user;
};
# backups
backup.group."gitea".paths = [
config.services.gitea.stateDir
];
services.nginx.enable = true;
services.nginx.virtualHosts.${cfg.hostname} = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString cfg.settings.server.HTTP_PORT}";
proxyPass = "http://localhost:${toString cfg.httpPort}";
};
};
};
}
}

View File

@ -20,6 +20,6 @@ in
hydraURL = "https://${domain}";
useSubstitutes = true;
notificationSender = notifyEmail;
buildMachinesFiles = [ ];
buildMachinesFiles = [];
};
}
}

View File

@ -7,8 +7,7 @@
let
cfg = config.services.icecast;
in
{
in {
options.services.icecast = {
mount = lib.mkOption {
type = lib.types.str;
@ -18,13 +17,11 @@ in
type = lib.types.str;
example = "fallback.mp3";
};
nginx = lib.mkEnableOption "enable nginx";
};
config = lib.mkIf cfg.enable {
services.icecast = {
listen.address = "0.0.0.0";
listen.port = 8001;
listen.address = "127.0.0.1";
admin.password = "hackme";
extraConf = ''
<authentication>
@ -51,14 +48,11 @@ in
</mount>
'';
};
services.nginx.virtualHosts.${cfg.hostname} = lib.mkIf cfg.nginx {
services.nginx.virtualHosts.${cfg.hostname} = {
enableACME = true;
forceSSL = true;
locations."/${cfg.mount}" = {
proxyPass = "http://localhost:${toString cfg.listen.port}/${cfg.mount}";
extraConfig = ''
add_header Access-Control-Allow-Origin *;
'';
};
};
};

View File

@ -1,21 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.iodine.server;
in
{
config = lib.mkIf cfg.enable {
# iodine DNS-based vpn
services.iodine.server = {
ip = "192.168.99.1";
domain = "tun.neet.dev";
passwordFile = "/run/agenix/iodine";
};
age.secrets.iodine.file = ../../secrets/iodine.age;
networking.firewall.allowedUDPPorts = [ 53 ];
networking.nat.internalInterfaces = [
"dns0" # iodine
];
};
}

View File

@ -1,69 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.librechat;
in
{
options.services.librechat = {
enable = mkEnableOption "librechat";
port = mkOption {
type = types.int;
default = 3080;
};
host = lib.mkOption {
type = lib.types.str;
example = "example.com";
};
};
config = mkIf cfg.enable {
virtualisation.oci-containers.containers = {
librechat = {
image = "ghcr.io/danny-avila/librechat:v0.7.7";
environment = {
HOST = "0.0.0.0";
MONGO_URI = "mongodb://host.containers.internal:27017/LibreChat";
ENDPOINTS = "openAI,google,bingAI,gptPlugins";
OPENAI_MODELS = lib.concatStringsSep "," [
"gpt-4o-mini"
"o3-mini"
"gpt-4o"
"o1"
];
REFRESH_TOKEN_EXPIRY = toString (1000 * 60 * 60 * 24 * 30); # 30 days
};
environmentFiles = [
"/run/agenix/librechat-env-file"
];
ports = [
"${toString cfg.port}:3080"
];
};
};
age.secrets.librechat-env-file.file = ../../secrets/librechat-env-file.age;
services.mongodb.enable = true;
services.mongodb.bind_ip = "0.0.0.0";
# easier podman maintenance
virtualisation.oci-containers.backend = "podman";
virtualisation.podman.dockerSocket.enable = true;
virtualisation.podman.dockerCompat = true;
# For mongodb access
networking.firewall.trustedInterfaces = [
"podman0" # for librechat
];
services.nginx.virtualHosts.${cfg.host} = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString cfg.port}";
proxyWebsockets = true;
};
};
};
}

View File

@ -1,113 +0,0 @@
{ config, pkgs, lib, ... }:
with builtins;
let
cfg = config.mailserver;
domains = [
"neet.space"
"neet.dev"
"neet.cloud"
"runyan.org"
"runyan.rocks"
"thunderhex.com"
"tar.ninja"
"bsd.ninja"
"bsd.rocks"
];
in
{
config = lib.mkIf cfg.enable {
# kresd doesn't work with tailscale MagicDNS
mailserver.localDnsResolver = false;
services.resolved.enable = true;
mailserver = {
fqdn = "mail.neet.dev";
dkimKeyBits = 2048;
indexDir = "/var/lib/mailindex";
enableManageSieve = true;
fullTextSearch.enable = true;
fullTextSearch.indexAttachments = true;
fullTextSearch.memoryLimit = 500;
inherit domains;
loginAccounts = {
"jeremy@runyan.org" = {
hashedPasswordFile = "/run/agenix/hashed-email-pw";
# catchall for all domains
aliases = map (domain: "@${domain}") domains;
};
"cris@runyan.org" = {
hashedPasswordFile = "/run/agenix/cris-hashed-email-pw";
aliases = [ "chris@runyan.org" ];
};
"robot@runyan.org" = {
aliases = [
"no-reply@neet.dev"
"robot@neet.dev"
];
sendOnly = true;
hashedPasswordFile = "/run/agenix/hashed-robots-email-pw";
};
};
rejectRecipients = [
"george@runyan.org"
"joslyn@runyan.org"
"damon@runyan.org"
"jonas@runyan.org"
"simon@neet.dev"
"ellen@runyan.org"
];
forwards = {
"amazon@runyan.org" = [
"jeremy@runyan.org"
"cris@runyan.org"
];
};
certificateScheme = "acme-nginx"; # use let's encrypt for certs
};
age.secrets.hashed-email-pw.file = ../../secrets/hashed-email-pw.age;
age.secrets.cris-hashed-email-pw.file = ../../secrets/cris-hashed-email-pw.age;
age.secrets.hashed-robots-email-pw.file = ../../secrets/hashed-robots-email-pw.age;
# sendmail to use xxx@domain instead of xxx@mail.domain
services.postfix.origin = "$mydomain";
# relay sent mail through mailgun
# https://www.howtoforge.com/community/threads/different-smtp-relays-for-different-domains-in-postfix.82711/#post-392620
services.postfix.config = {
smtp_sasl_auth_enable = "yes";
smtp_sasl_security_options = "noanonymous";
smtp_sasl_password_maps = "hash:/var/lib/postfix/conf/sasl_relay_passwd";
smtp_use_tls = "yes";
sender_dependent_relayhost_maps = "hash:/var/lib/postfix/conf/sender_relay";
smtp_sender_dependent_authentication = "yes";
};
services.postfix.mapFiles.sender_relay =
let
relayHost = "[smtp.mailgun.org]:587";
in
pkgs.writeText "sender_relay"
(concatStringsSep "\n" (map (domain: "@${domain} ${relayHost}") domains));
services.postfix.mapFiles.sasl_relay_passwd = "/run/agenix/sasl_relay_passwd";
age.secrets.sasl_relay_passwd.file = ../../secrets/sasl_relay_passwd.age;
# webmail
services.nginx.enable = true;
services.roundcube = {
enable = true;
hostName = config.mailserver.fqdn;
extraConfig = ''
# starttls needed for authentication, so the fqdn required to match the certificate
$config['smtp_server'] = "tls://${config.mailserver.fqdn}";
$config['smtp_user'] = "%u";
$config['smtp_pass'] = "%p";
'';
};
# backups
backup.group."email".paths = [
config.mailserver.mailDirectory
];
};
}

View File

@ -3,8 +3,7 @@
let
cfg = config.services.matrix;
certs = config.security.acme.certs;
in
{
in {
options.services.matrix = {
enable = lib.mkEnableOption "enable matrix";
element-web = {
@ -60,25 +59,23 @@ in
config = lib.mkIf cfg.enable {
services.matrix-synapse = {
enable = true;
settings = {
server_name = cfg.host;
enable_registration = cfg.enable_registration;
listeners = [{
bind_addresses = [ "127.0.0.1" ];
port = cfg.port;
tls = false;
resources = [{
compress = true;
names = [ "client" "federation" ];
}];
}];
turn_uris = [
"turn:${cfg.turn.host}:${toString cfg.turn.port}?transport=udp"
"turn:${cfg.turn.host}:${toString cfg.turn.port}?transport=tcp"
];
turn_shared_secret = cfg.turn.secret;
turn_user_lifetime = "1h";
};
server_name = cfg.host;
enable_registration = cfg.enable_registration;
listeners = [ {
bind_address = "127.0.0.1";
port = cfg.port;
tls = false;
resources = [ {
compress = true;
names = [ "client" "webclient" "federation" ];
} ];
} ];
turn_uris = [
"turn:${cfg.turn.host}:${toString cfg.turn.port}?transport=udp"
"turn:${cfg.turn.host}:${toString cfg.turn.port}?transport=tcp"
];
turn_shared_secret = cfg.turn.secret;
turn_user_lifetime = "1h";
};
services.coturn = {
@ -121,7 +118,7 @@ in
services.nginx = {
enable = true;
virtualHosts.${cfg.host} = {
virtualHosts.${cfg.host} = {
enableACME = true;
forceSSL = true;
listen = [
@ -138,8 +135,7 @@ in
];
locations."/".proxyPass = "http://localhost:${toString cfg.port}";
};
virtualHosts.${cfg.turn.host} = {
# get TLS cert for TURN server
virtualHosts.${cfg.turn.host} = { # get TLS cert for TURN server
enableACME = true;
forceSSL = true;
};
@ -188,32 +184,26 @@ in
min = 5;
max = 30;
};
# startScreenSharing = true;
startScreenSharing = true;
videoQuality = {
disabledCodec = "VP8";
preferredCodec = "H264";
enforcePreferredCodec = true;
};
# p2p = {
# enabled = true;
# preferH264 = true;
# disabledCodec = "VP8";
# preferredCodec = "H264";
# disableH264 = false;
# };
p2p = {
enabled = true;
preferH264 = true;
disabledCodec = "VP8";
preferredCodec = "H264";
disableH264 = false;
};
requireDisplayName = false;
disableThirdPartyRequests = true;
localRecording.enabled = false;
localRecording = {
enabled = false;
};
doNotStoreRoom = true;
};
interfaceConfig = {
SHOW_JITSI_WATERMARK = false;
SHOW_WATERMARK_FOR_GUESTS = false;
};
};
services.jitsi-videobridge = lib.mkIf cfg.jitsi-meet.enable {
enable = true;
openFirewall = true;
};
};
}
}

View File

@ -3,8 +3,7 @@
let
cfg = config.services.murmur;
certs = config.security.acme.certs;
in
{
in {
options.services.murmur.domain = lib.mkOption {
type = lib.types.str;
};

View File

@ -1,44 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.nextcloud;
in
{
config = lib.mkIf cfg.enable {
services.nextcloud = {
https = true;
package = pkgs.nextcloud30;
hostName = "neet.cloud";
config.dbtype = "sqlite";
config.adminuser = "jeremy";
config.adminpassFile = "/run/agenix/nextcloud-pw";
autoUpdateApps.enable = true;
extraApps = with config.services.nextcloud.package.packages.apps; {
# Want
inherit end_to_end_encryption mail spreed;
# Might use
inherit bookmarks calendar cookbook deck memories onlyoffice qownnotesapi;
# Try out
# inherit maps music news notes phonetrack polls forms;
};
extraAppsEnable = true;
};
age.secrets.nextcloud-pw = {
file = ../../secrets/nextcloud-pw.age;
owner = "nextcloud";
};
# backups
backup.group."nextcloud".paths = [
config.services.nextcloud.home
];
services.nginx.virtualHosts.${config.services.nextcloud.hostName} = {
enableACME = true;
forceSSL = true;
};
};
}

View File

@ -5,8 +5,7 @@ let
nginxWithRTMP = pkgs.nginx.override {
modules = [ pkgs.nginxModules.rtmp ];
};
in
{
in {
options.services.nginx.stream = {
enable = lib.mkEnableOption "enable nginx rtmp/hls/dash video streaming";
port = lib.mkOption {
@ -52,7 +51,7 @@ in
appendConfig = ''
rtmp {
server {
listen ${toString cfg.port};
listen 1935;
chunk_size 4096;
application ${cfg.rtmpName} {
allow publish all;
@ -73,4 +72,4 @@ in
cfg.port
];
};
}
}

View File

@ -2,12 +2,7 @@
let
cfg = config.services.nginx;
in
{
options.services.nginx = {
openFirewall = lib.mkEnableOption "Open firewall ports 80 and 443";
};
in {
config = lib.mkIf cfg.enable {
services.nginx = {
recommendedGzipSettings = true;
@ -16,8 +11,6 @@ in
recommendedTlsSettings = true;
};
services.nginx.openFirewall = lib.mkDefault true;
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ 80 443 ];
networking.firewall.allowedTCPPorts = [ 80 443 ];
};
}
}

View File

@ -1,32 +0,0 @@
{ lib, config, ... }:
with lib;
let
cfg = config.services.owncast;
in
{
options.services.owncast = {
hostname = lib.mkOption {
type = types.str;
example = "example.com";
};
};
config = mkIf cfg.enable {
services.owncast.listen = "127.0.0.1";
services.owncast.port = 62419; # random port
networking.firewall.allowedTCPPorts = [ cfg.rtmp-port ];
services.nginx.enable = true;
services.nginx.virtualHosts.${cfg.hostname} = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString cfg.port}";
proxyWebsockets = true;
};
};
};
}

View File

@ -0,0 +1,42 @@
;<?php http_response_code(403); /*
[main]
name = "Kode Paste"
discussion = false
opendiscussion = false
password = true
fileupload = false
burnafterreadingselected = false
defaultformatter = "plaintext"
sizelimit = 10485760
template = "bootstrap"
languageselection = false
[expire]
default = "1week"
[expire_options]
5min = 300
10min = 600
1hour = 3600
1day = 86400
1week = 604800
[formatter_options]
plaintext = "Plain Text"
syntaxhighlighting = "Source Code"
markdown = "Markdown"
[traffic]
limit = 10
dir = "/var/lib/privatebin"
[purge]
limit = 300
batchsize = 10
dir = "/var/lib/privatebin"
[model]
class = Filesystem
[model_options]
dir = "/var/lib/privatebin"

View File

@ -0,0 +1,73 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.privatebin;
privateBinSrc = pkgs.stdenv.mkDerivation {
name = "privatebin";
src = pkgs.fetchFromGitHub {
owner = "privatebin";
repo = "privatebin";
rev = "d65bf02d7819a530c3c2a88f6f9947651fe5258d";
sha256 = "7ttAvEDL1ab0cUZcqZzXFkXwB2rF2t4eNpPxt48ap94=";
};
installPhase = ''
cp -ar $src $out
'';
};
in {
options.services.privatebin = {
enable = lib.mkEnableOption "enable privatebin";
host = lib.mkOption {
type = lib.types.str;
example = "example.com";
};
};
config = lib.mkIf cfg.enable {
users.users.privatebin = {
description = "privatebin service user";
group = "privatebin";
isSystemUser = true;
};
users.groups.privatebin = {};
services.nginx.enable = true;
services.nginx.virtualHosts.${cfg.host} = {
enableACME = true;
forceSSL = true;
locations."/" = {
root = privateBinSrc;
index = "index.php";
};
locations."~ \.php$" = {
root = privateBinSrc;
extraConfig = ''
fastcgi_pass unix:${config.services.phpfpm.pools.privatebin.socket};
fastcgi_index index.php;
'';
};
};
systemd.tmpfiles.rules = [
"d '/var/lib/privatebin' 0750 privatebin privatebin - -"
];
services.phpfpm.pools.privatebin = {
user = "privatebin";
group = "privatebin";
phpEnv = {
CONFIG_PATH = "${./conf.php}";
};
settings = {
pm = "dynamic";
"listen.owner" = config.services.nginx.user;
"pm.max_children" = 5;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 1;
"pm.max_spare_servers" = 3;
"pm.max_requests" = 500;
};
};
};
}

View File

@ -1,75 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.radio;
radioPackage = config.inputs.radio.packages.${config.currentSystem}.radio;
in
{
options.services.radio = {
enable = lib.mkEnableOption "enable radio";
user = lib.mkOption {
type = lib.types.str;
default = "radio";
description = ''
The user radio should run as
'';
};
group = lib.mkOption {
type = lib.types.str;
default = "radio";
description = ''
The group radio should run as
'';
};
dataDir = lib.mkOption {
type = lib.types.str;
default = "/var/lib/radio";
description = ''
Path to the radio data directory
'';
};
host = lib.mkOption {
type = lib.types.str;
description = ''
Domain radio is hosted on
'';
};
nginx = lib.mkEnableOption "enable nginx";
};
config = lib.mkIf cfg.enable {
services.icecast = {
enable = true;
hostname = cfg.host;
mount = "stream.mp3";
fallback = "fallback.mp3";
};
services.nginx.virtualHosts.${cfg.host} = lib.mkIf cfg.nginx {
enableACME = true;
forceSSL = true;
locations."/".root = config.inputs.radio-web;
};
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
home = cfg.dataDir;
createHome = true;
};
users.groups.${cfg.group} = { };
systemd.services.radio = {
enable = true;
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig.ExecStart = "${radioPackage}/bin/radio ${config.services.icecast.listen.address}:${toString config.services.icecast.listen.port} ${config.services.icecast.mount} 5500";
serviceConfig.User = cfg.user;
serviceConfig.Group = cfg.group;
serviceConfig.WorkingDirectory = cfg.dataDir;
preStart = ''
mkdir -p ${cfg.dataDir}
chown ${cfg.user} ${cfg.dataDir}
'';
};
};
}

View File

@ -1,118 +0,0 @@
{ config, lib, pkgs, ... }:
{
config = lib.mkIf config.services.samba.enable {
services.samba = {
openFirewall = true;
package = pkgs.sambaFull; # printer sharing
# should this be on?
nsswins = true;
settings = {
global = {
security = "user";
workgroup = "HOME";
"server string" = "smbnix";
"netbios name" = "smbnix";
"use sendfile" = "yes";
"min protocol" = "smb2";
"guest account" = "nobody";
"map to guest" = "bad user";
# printing
"load printers" = "yes";
printing = "cups";
"printcap name" = "cups";
"hide files" = "/.nobackup/.DS_Store/._.DS_Store/";
};
public = {
path = "/data/samba/Public";
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
"create mask" = "0644";
"directory mask" = "0755";
"force user" = "public_data";
"force group" = "public_data";
};
googlebot = {
path = "/data/samba/googlebot";
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
"valid users" = "googlebot";
"create mask" = "0644";
"directory mask" = "0755";
"force user" = "googlebot";
"force group" = "users";
};
cris = {
path = "/data/samba/cris";
browseable = "yes";
"read only" = "no";
"guest ok" = "no";
"valid users" = "cris";
"create mask" = "0644";
"directory mask" = "0755";
"force user" = "root";
"force group" = "users";
};
printers = {
comment = "All Printers";
path = "/var/spool/samba";
public = "yes";
browseable = "yes";
# to allow user 'guest account' to print.
"guest ok" = "yes";
writable = "no";
printable = "yes";
"create mode" = 0700;
};
};
};
# backups
backup.group."samba".paths = [
config.services.samba.settings.googlebot.path
config.services.samba.settings.cris.path
config.services.samba.settings.public.path
];
# Windows discovery of samba server
services.samba-wsdd = {
enable = true;
# are these needed?
workgroup = "HOME";
hoplimit = 3;
discovery = true;
};
networking.firewall.allowedTCPPorts = [ 5357 ];
networking.firewall.allowedUDPPorts = [ 3702 ];
# Printer discovery
# (is this needed?)
services.avahi.enable = true;
services.avahi.nssmdns4 = true;
# printer sharing
systemd.tmpfiles.rules = [
"d /var/spool/samba 1777 root root -"
];
users.groups.public_data.gid = 994;
users.users.public_data = {
isSystemUser = true;
group = "public_data";
uid = 994;
};
users.users.googlebot.extraGroups = [ "public_data" ];
# samba user for share
users.users.cris.isSystemUser = true;
users.users.cris.group = "cris";
users.groups.cris = { };
};
}

View File

@ -1,30 +0,0 @@
{ config, pkgs, lib, ... }:
let
cfg = config.services.searx;
in
{
config = lib.mkIf cfg.enable {
services.searx = {
environmentFile = "/run/agenix/searx";
settings = {
server.port = 43254;
server.secret_key = "@SEARX_SECRET_KEY@";
engines = [{
name = "wolframalpha";
shortcut = "wa";
api_key = "@WOLFRAM_API_KEY@";
engine = "wolframalpha_api";
}];
};
};
services.nginx.virtualHosts."search.neet.space" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString config.services.searx.settings.server.port}";
};
};
age.secrets.searx.file = ../../secrets/searx.age;
};
}

View File

@ -2,8 +2,7 @@
let
cfg = config.services.thelounge;
in
{
in {
options.services.thelounge = {
fileUploadBaseUrl = lib.mkOption {
type = lib.types.str;
@ -24,12 +23,12 @@ in
config = lib.mkIf cfg.enable {
services.thelounge = {
public = false;
private = true;
extraConfig = {
reverseProxy = true;
maxHistory = -1;
https.enable = false;
# theme = "thelounge-theme-solarized";
# theme = "thelounge-theme-solarized";
prefetch = false;
prefetchStorage = false;
fileUpload = {
@ -43,10 +42,6 @@ in
};
};
backup.group."thelounge".paths = [
"/var/lib/thelounge/"
];
# the lounge client
services.nginx.virtualHosts.${cfg.host} = {
enableACME = true;

View File

@ -1,26 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.unifi;
in
{
options.services.unifi = {
# Open select Unifi ports instead of using openFirewall to avoid opening access to unifi's control panel
openMinimalFirewall = lib.mkEnableOption "Open bare minimum firewall ports";
};
config = lib.mkIf cfg.enable {
services.unifi.unifiPackage = pkgs.unifi;
services.unifi.mongodbPackage = pkgs.mongodb-7_0;
networking.firewall = lib.mkIf cfg.openMinimalFirewall {
allowedUDPPorts = [
3478 # STUN
10001 # used for device discovery.
];
allowedTCPPorts = [
8080 # Used for device and application communication.
];
};
};
}

View File

@ -15,14 +15,14 @@ let
in
{
networking.firewall.allowedUDPPorts = [ rtp-port ];
networking.firewall.allowedTCPPortRanges = [{
networking.firewall.allowedTCPPortRanges = [ {
from = webrtc-peer-lower-port;
to = webrtc-peer-upper-port;
}];
networking.firewall.allowedUDPPortRanges = [{
} ];
networking.firewall.allowedUDPPortRanges = [ {
from = webrtc-peer-lower-port;
to = webrtc-peer-upper-port;
}];
} ];
virtualisation.docker.enable = true;
@ -49,12 +49,12 @@ in
ports = [
"${toStr ingest-port}:8084"
];
# imageFile = pkgs.dockerTools.pullImage {
# imageName = "projectlightspeed/ingest";
# finalImageTag = "version-0.1.4";
# imageDigest = "sha256:9fc51833b7c27a76d26e40f092b9cec1ac1c4bfebe452e94ad3269f1f73ff2fc";
# sha256 = "19kxl02x0a3i6hlnsfcm49hl6qxnq2f3hfmyv1v8qdaz58f35kd5";
# };
# imageFile = pkgs.dockerTools.pullImage {
# imageName = "projectlightspeed/ingest";
# finalImageTag = "version-0.1.4";
# imageDigest = "sha256:9fc51833b7c27a76d26e40f092b9cec1ac1c4bfebe452e94ad3269f1f73ff2fc";
# sha256 = "19kxl02x0a3i6hlnsfcm49hl6qxnq2f3hfmyv1v8qdaz58f35kd5";
# };
};
"lightspeed-react" = {
workdir = "/var/lib/lightspeed-react";
@ -62,12 +62,12 @@ in
ports = [
"${toStr web-port}:80"
];
# imageFile = pkgs.dockerTools.pullImage {
# imageName = "projectlightspeed/react";
# finalImageTag = "version-0.1.3";
# imageDigest = "sha256:b7c58425f1593f7b4304726b57aa399b6e216e55af9c0962c5c19333fae638b6";
# sha256 = "0d2jh7mr20h7dxgsp7ml7cw2qd4m8ja9rj75dpy59zyb6v0bn7js";
# };
# imageFile = pkgs.dockerTools.pullImage {
# imageName = "projectlightspeed/react";
# finalImageTag = "version-0.1.3";
# imageDigest = "sha256:b7c58425f1593f7b4304726b57aa399b6e216e55af9c0962c5c19333fae638b6";
# sha256 = "0d2jh7mr20h7dxgsp7ml7cw2qd4m8ja9rj75dpy59zyb6v0bn7js";
# };
};
"lightspeed-webrtc" = {
workdir = "/var/lib/lightspeed-webrtc";
@ -79,18 +79,15 @@ in
"${toStr webrtc-peer-lower-port}-${toStr webrtc-peer-upper-port}:${toStr webrtc-peer-lower-port}-${toStr webrtc-peer-upper-port}/udp"
];
cmd = [
"lightspeed-webrtc"
"--addr=0.0.0.0"
"--ip=${domain}"
"--ports=${toStr webrtc-peer-lower-port}-${toStr webrtc-peer-upper-port}"
"run"
"lightspeed-webrtc" "--addr=0.0.0.0" "--ip=${domain}"
"--ports=${toStr webrtc-peer-lower-port}-${toStr webrtc-peer-upper-port}" "run"
];
# imageFile = pkgs.dockerTools.pullImage {
# imageName = "projectlightspeed/webrtc";
# finalImageTag = "version-0.1.2";
# imageDigest = "sha256:ddf8b3dd294485529ec11d1234a3fc38e365a53c4738998c6bc2c6930be45ecf";
# sha256 = "1bdy4ak99fjdphj5bsk8rp13xxmbqdhfyfab14drbyffivg9ad2i";
# };
# imageFile = pkgs.dockerTools.pullImage {
# imageName = "projectlightspeed/webrtc";
# finalImageTag = "version-0.1.2";
# imageDigest = "sha256:ddf8b3dd294485529ec11d1234a3fc38e365a53c4738998c6bc2c6930be45ecf";
# sha256 = "1bdy4ak99fjdphj5bsk8rp13xxmbqdhfyfab14drbyffivg9ad2i";
# };
};
};
};

View File

@ -1,8 +1,8 @@
import ./module.nix ({ name, description, serviceConfig }:
{
systemd.user.services.${name} = {
inherit description serviceConfig;
wantedBy = [ "default.target" ];
};
})
{
systemd.user.services.${name} = {
inherit description serviceConfig;
wantedBy = [ "default.target" ];
};
})

View File

@ -1,15 +1,15 @@
import ./module.nix ({ name, description, serviceConfig }:
{
systemd.user.services.${name} = {
Unit = {
Description = description;
};
Service = serviceConfig;
Install = {
WantedBy = [ "default.target" ];
};
{
systemd.user.services.${name} = {
Unit = {
Description = description;
};
})
Service = serviceConfig;
Install = {
WantedBy = [ "default.target" ];
};
};
})

View File

@ -2,8 +2,7 @@
let
cfg = config.services.zerobin;
in
{
in {
options.services.zerobin = {
host = lib.mkOption {
type = lib.types.str;

View File

@ -1,54 +0,0 @@
{ config, lib, pkgs, ... }:
# Improvements to the default shell
# - use nix-index for command-not-found
# - disable fish's annoying greeting message
# - add some handy shell commands
{
environment.systemPackages = with pkgs; [
comma
];
# nix-index
programs.nix-index.enable = true;
programs.nix-index.enableFishIntegration = true;
programs.command-not-found.enable = false;
programs.fish = {
enable = true;
shellInit = ''
# disable annoying fish shell greeting
set fish_greeting
alias sudo="doas"
'';
};
environment.shellAliases = {
myip = "dig +short myip.opendns.com @resolver1.opendns.com";
# https://linuxreviews.org/HOWTO_Test_Disk_I/O_Performance
io_seq_read = "${pkgs.fio}/bin/fio --name TEST --eta-newline=5s --filename=temp.file --rw=read --size=2g --io_size=10g --blocksize=1024k --ioengine=libaio --fsync=10000 --iodepth=32 --direct=1 --numjobs=1 --runtime=60 --group_reporting; rm temp.file";
io_seq_write = "${pkgs.fio}/bin/fio --name TEST --eta-newline=5s --filename=temp.file --rw=write --size=2g --io_size=10g --blocksize=1024k --ioengine=libaio --fsync=10000 --iodepth=32 --direct=1 --numjobs=1 --runtime=60 --group_reporting; rm temp.file";
io_rand_read = "${pkgs.fio}/bin/fio --name TEST --eta-newline=5s --filename=temp.file --rw=randread --size=2g --io_size=10g --blocksize=4k --ioengine=libaio --fsync=1 --iodepth=1 --direct=1 --numjobs=32 --runtime=60 --group_reporting; rm temp.file";
io_rand_write = "${pkgs.fio}/bin/fio --name TEST --eta-newline=5s --filename=temp.file --rw=randrw --size=2g --io_size=10g --blocksize=4k --ioengine=libaio --fsync=1 --iodepth=1 --direct=1 --numjobs=1 --runtime=60 --group_reporting; rm temp.file";
llsblk = "lsblk -o +uuid,fsType";
};
nixpkgs.overlays = [
(final: prev: {
# comma uses the "nix-index" package built into nixpkgs by default.
# That package doesn't use the prebuilt nix-index database so it needs to be changed.
comma = prev.comma.overrideAttrs (old: {
postInstall = ''
wrapProgram $out/bin/comma \
--prefix PATH : ${lib.makeBinPath [ prev.fzy config.programs.nix-index.package ]}
ln -s $out/bin/comma $out/bin/,
'';
});
})
];
}

View File

@ -1,38 +0,0 @@
{ config, lib, pkgs, ... }:
{
programs.ssh.knownHosts = lib.filterAttrs (n: v: v != null) (lib.concatMapAttrs
(host: cfg: {
${host} = {
hostNames = cfg.hostNames;
publicKey = cfg.hostKey;
};
"${host}-remote-unlock" =
if cfg.remoteUnlock != null then {
hostNames = builtins.filter (h: h != null) [ cfg.remoteUnlock.clearnetHost cfg.remoteUnlock.onionHost ];
publicKey = cfg.remoteUnlock.hostKey;
} else null;
})
config.machines.hosts);
# prebuilt cmds for easy ssh LUKS unlock
environment.shellAliases =
let
unlockHosts = unlockType: lib.concatMapAttrs
(host: cfg:
if cfg.remoteUnlock != null && cfg.remoteUnlock.${unlockType} != null then {
${host} = cfg.remoteUnlock.${unlockType};
} else { })
config.machines.hosts;
in
lib.concatMapAttrs (host: addr: { "unlock-over-tor_${host}" = "torsocks ssh root@${addr}"; }) (unlockHosts "onionHost")
//
lib.concatMapAttrs (host: addr: { "unlock_${host}" = "ssh root@${addr}"; }) (unlockHosts "clearnetHost");
# TODO: Old ssh keys I will remove some day...
machines.ssh.userKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMVR/R3ZOsv7TZbICGBCHdjh1NDT8SnswUyINeJOC7QG"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE0dcqL/FhHmv+a1iz3f9LJ48xubO7MZHy35rW9SZOYM"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHSkKiRUUmnErOKGx81nyge/9KqjkPh8BfDk0D3oP586" # nat
];
}

14
common/zerotier.nix Normal file
View File

@ -0,0 +1,14 @@
{ lib, config, ... }:
let
cfg = config.services.zerotierone;
in {
config = lib.mkIf cfg.enable {
services.zerotierone.joinNetworks = [
"565799d8f6d654c0"
];
networking.firewall.allowedUDPPorts = [
9993
];
};
}

309
flake.lock generated
View File

@ -1,325 +1,74 @@
{
"nodes": {
"agenix": {
"inputs": {
"darwin": "darwin",
"home-manager": [
"home-manager"
],
"nixpkgs": [
"nixpkgs"
],
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1723293904,
"narHash": "sha256-b+uqzj+Wa6xgMS9aNbX4I+sXeb5biPDi39VgvSFqFvU=",
"owner": "ryantm",
"repo": "agenix",
"rev": "f6291c5935fdc4e0bef208cfc0dcab7e3f7a1c41",
"type": "github"
},
"original": {
"owner": "ryantm",
"repo": "agenix",
"type": "github"
}
},
"blobs": {
"flake": false,
"locked": {
"lastModified": 1604995301,
"narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"type": "gitlab"
}
},
"dailybuild_modules": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1739947126,
"narHash": "sha256-JoiddH5H9up8jC/VKU8M7wDlk/bstKoJ3rHj+TkW4Zo=",
"ref": "refs/heads/master",
"rev": "ea1ad60f1c6662103ef4a3705d8e15aa01219529",
"revCount": 20,
"type": "git",
"url": "https://git.neet.dev/zuckerberg/dailybot.git"
},
"original": {
"type": "git",
"url": "https://git.neet.dev/zuckerberg/dailybot.git"
}
},
"darwin": {
"inputs": {
"nixpkgs": [
"agenix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1700795494,
"narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d",
"type": "github"
},
"original": {
"owner": "lnl7",
"ref": "master",
"repo": "nix-darwin",
"type": "github"
}
},
"deploy-rs": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"nixpkgs": [
"nixpkgs"
],
"utils": [
"flake-utils"
]
},
"locked": {
"lastModified": 1727447169,
"narHash": "sha256-3KyjMPUKHkiWhwR91J1YchF6zb6gvckCAY1jOE+ne0U=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "aa07eb05537d4cd025e2310397a6adcedfe72c76",
"type": "github"
},
"original": {
"owner": "serokell",
"repo": "deploy-rs",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1740845322,
"narHash": "sha256-AXEgFj3C0YJhu9k1OhbRhiA6FnDr81dQZ65U3DhaWpw=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "fcac3d6d88302a5e64f6cb8014ac785e08874c8d",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"nix-index-database": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1728263287,
"narHash": "sha256-GJDtsxz2/zw6g/Nrp4XVWBS5IaZ7ZUkuvxPOBEDe7pg=",
"owner": "Mic92",
"repo": "nix-index-database",
"rev": "5fce10c871bab6d7d5ac9e5e7efbb3a2783f5259",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "nix-index-database",
"type": "github"
}
},
"nixos-hardware": {
"locked": {
"lastModified": 1728056216,
"narHash": "sha256-IrO06gFUDTrTlIP3Sz+mRB6WUoO2YsgMtOD3zi0VEt0=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "b7ca02c7565fbf6d27ff20dd6dbd49c5b82eef28",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "master",
"repo": "nixos-hardware",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1740374225,
"narHash": "sha256-Dnmzy5YWUVj3BNaZo5jRpZslXexbNKEk3ADGGcz9RpY=",
"lastModified": 1622622179,
"narHash": "sha256-XCw/9QDuj9J6prVR8YrteTcFKj2sRWYIjwgs8qOOrYQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3349acd765bdffe454f7c8bbc450855577c1a6cf",
"rev": "eaba7870ffc3400eca4407baa24184b7fe337ec1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "master",
"ref": "nixos-21.05",
"repo": "nixpkgs",
"type": "github"
}
},
"radio": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"nixpkgs_2": {
"locked": {
"lastModified": 1631585589,
"narHash": "sha256-q4o/4/2pEuJyaKZwNQC5KHnzG1obClzFB7zWk9XSDfY=",
"ref": "main",
"rev": "5bf607fed977d41a269942a7d1e92f3e6d4f2473",
"revCount": 38,
"type": "git",
"url": "https://git.neet.dev/zuckerberg/radio.git"
"lastModified": 1607522989,
"narHash": "sha256-o/jWhOSAlaK7y2M57OIriRt6whuVVocS/T0mG7fd1TI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e9158eca70ae59e73fae23be5d13d3fa0cfc78b4",
"type": "github"
},
"original": {
"ref": "main",
"rev": "5bf607fed977d41a269942a7d1e92f3e6d4f2473",
"type": "git",
"url": "https://git.neet.dev/zuckerberg/radio.git"
}
},
"radio-web": {
"flake": false,
"locked": {
"lastModified": 1652121792,
"narHash": "sha256-j1Y9MAjUVNgyFSeGzPoqibAnEysJDjZSXukVfQ7+bsQ=",
"ref": "refs/heads/master",
"rev": "72e7a9e80b780c84ed8d4a6374bfbb242701f900",
"revCount": 5,
"type": "git",
"url": "https://git.neet.dev/zuckerberg/radio-web.git"
},
"original": {
"type": "git",
"url": "https://git.neet.dev/zuckerberg/radio-web.git"
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"agenix": "agenix",
"dailybuild_modules": "dailybuild_modules",
"deploy-rs": "deploy-rs",
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"home-manager": "home-manager",
"nix-index-database": "nix-index-database",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs",
"radio": "radio",
"radio-web": "radio-web",
"simple-nixos-mailserver": "simple-nixos-mailserver",
"systems": "systems"
"simple-nixos-mailserver": "simple-nixos-mailserver"
}
},
"simple-nixos-mailserver": {
"inputs": {
"blobs": "blobs",
"flake-compat": [
"flake-compat"
],
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-24_05": [
"nixpkgs"
]
"nixpkgs": "nixpkgs_2",
"utils": "utils"
},
"locked": {
"lastModified": 1722877200,
"narHash": "sha256-qgKDNJXs+od+1UbRy62uk7dYal3h98I4WojfIqMoGcg=",
"lastModified": 1622448045,
"narHash": "sha256-duIjiaBwdkY+JpeVbQSt0SOrYQ4GDH5XoT3xZOz4jPA=",
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"rev": "af7d3bf5daeba3fc28089b015c0dd43f06b176f2",
"rev": "6e0690093c24261192ee5c0557f85791a67eec29",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"ref": "master",
"ref": "nixos-21.05",
"repo": "nixos-mailserver",
"type": "gitlab"
}
},
"systems": {
"utils": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"lastModified": 1605370193,
"narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5021eac20303a61fafe17224c087f5519baed54d",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}

217
flake.nix
View File

@ -1,203 +1,32 @@
{
inputs = {
# nixpkgs
nixpkgs.url = "github:NixOS/nixpkgs/master";
# Common Utils Among flake inputs
systems.url = "github:nix-systems/default";
flake-utils = {
url = "github:numtide/flake-utils";
inputs.systems.follows = "systems";
};
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
# NixOS hardware
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
# Home Manager
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
# Mail Server
simple-nixos-mailserver = {
url = "gitlab:simple-nixos-mailserver/nixos-mailserver/master";
inputs = {
nixpkgs.follows = "nixpkgs";
nixpkgs-24_05.follows = "nixpkgs";
flake-compat.follows = "flake-compat";
};
};
# Agenix
agenix = {
url = "github:ryantm/agenix";
inputs = {
nixpkgs.follows = "nixpkgs";
systems.follows = "systems";
home-manager.follows = "home-manager";
};
};
# Radio
radio = {
url = "git+https://git.neet.dev/zuckerberg/radio.git?ref=main&rev=5bf607fed977d41a269942a7d1e92f3e6d4f2473";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
radio-web = {
url = "git+https://git.neet.dev/zuckerberg/radio-web.git";
flake = false;
};
# Dailybot
dailybuild_modules = {
url = "git+https://git.neet.dev/zuckerberg/dailybot.git";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
# NixOS deployment
deploy-rs = {
url = "github:serokell/deploy-rs";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-compat.follows = "flake-compat";
utils.follows = "flake-utils";
};
};
# Prebuilt nix-index database
nix-index-database = {
url = "github:Mic92/nix-index-database";
inputs.nixpkgs.follows = "nixpkgs";
};
nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05";
simple-nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-21.05";
};
outputs = { self, nixpkgs, ... }@inputs:
outputs = inputs: {
nixosConfigurations =
let
machines = (import ./common/machine-info/moduleless.nix
{
inherit nixpkgs;
assertionsModule = "${nixpkgs}/nixos/modules/misc/assertions.nix";
}).machines;
machineHosts = machines.hosts;
machineRoles = machines.roles;
mkSystem = system: path:
inputs.nixpkgs.lib.nixosSystem {
inherit system;
modules = [
path
inputs.simple-nixos-mailserver.nixosModule
];
specialArgs = { inherit inputs; };
};
in
{
nixosConfigurations =
let
modules = system: hostname: with inputs; [
./common
simple-nixos-mailserver.nixosModule
agenix.nixosModules.default
dailybuild_modules.nixosModule
nix-index-database.nixosModules.nix-index
home-manager.nixosModules.home-manager
self.nixosModules.kernel-modules
({ lib, ... }: {
config = {
nixpkgs.overlays = [ self.overlays.default ];
environment.systemPackages = [
agenix.packages.${system}.agenix
];
networking.hostName = hostname;
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.googlebot = import ./home/googlebot.nix {
inherit hostname;
inherit machineRoles;
};
};
# because nixos specialArgs doesn't work for containers... need to pass in inputs a different way
options.inputs = lib.mkOption { default = inputs; };
options.currentSystem = lib.mkOption { default = system; };
})
];
mkSystem = system: nixpkgs: path: hostname:
let
allModules = modules system hostname;
# allow patching nixpkgs, remove this hack once this is solved: https://github.com/NixOS/nix/issues/3920
patchedNixpkgsSrc = nixpkgs.legacyPackages.${system}.applyPatches {
name = "nixpkgs-patched";
src = nixpkgs;
patches = [
./patches/gamepadui.patch
./patches/dont-break-nix-serve.patch
];
};
patchedNixpkgs = nixpkgs.lib.fix (self: (import "${patchedNixpkgsSrc}/flake.nix").outputs { self = nixpkgs; });
in
patchedNixpkgs.lib.nixosSystem {
inherit system;
modules = allModules ++ [ path ];
specialArgs = {
inherit allModules;
lib = self.lib;
nixos-hardware = inputs.nixos-hardware;
};
};
in
nixpkgs.lib.mapAttrs
(hostname: cfg:
mkSystem cfg.arch nixpkgs cfg.configurationPath hostname)
machineHosts;
packages =
let
mkKexec = system:
(nixpkgs.lib.nixosSystem {
inherit system;
modules = [ ./machines/ephemeral/kexec.nix ];
}).config.system.build.kexec_tarball;
mkIso = system:
(nixpkgs.lib.nixosSystem {
inherit system;
modules = [ ./machines/ephemeral/iso.nix ];
}).config.system.build.isoImage;
in
{
"x86_64-linux"."kexec" = mkKexec "x86_64-linux";
"x86_64-linux"."iso" = mkIso "x86_64-linux";
"aarch64-linux"."kexec" = mkKexec "aarch64-linux";
"aarch64-linux"."iso" = mkIso "aarch64-linux";
};
overlays.default = import ./overlays { inherit inputs; };
nixosModules.kernel-modules = import ./overlays/kernel-modules;
deploy.nodes =
let
mkDeploy = configName: arch: hostname: {
inherit hostname;
magicRollback = false;
sshUser = "root";
profiles.system.path = inputs.deploy-rs.lib.${arch}.activate.nixos self.nixosConfigurations.${configName};
};
in
nixpkgs.lib.mapAttrs
(hostname: cfg:
mkDeploy hostname cfg.arch (builtins.head cfg.hostNames))
machineHosts;
checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) inputs.deploy-rs.lib;
lib = nixpkgs.lib.extend (final: prev: import ./lib { lib = nixpkgs.lib; });
"reg" = mkSystem "x86_64-linux" ./machines/reg/configuration.nix;
"mitty" = mkSystem "x86_64-linux" ./machines/mitty/configuration.nix;
"nanachi" = mkSystem "x86_64-linux" ./machines/nanachi/configuration.nix;
"riko" = mkSystem "x86_64-linux" ./machines/riko/configuration.nix;
"neetdev" = mkSystem "x86_64-linux" ./machines/neet.dev/configuration.nix;
"liza" = mkSystem "x86_64-linux" ./machines/liza/configuration.nix;
"s0" = mkSystem "aarch64-linux" ./machines/storage/s0/configuration.nix;
"n1" = mkSystem "aarch64-linux" ./machines/compute/n1/configuration.nix;
};
};
}

View File

@ -1,83 +0,0 @@
{ hostname, machineRoles }:
{ config, lib, pkgs, ... }:
let
# Check if the current machine has the role "personal"
thisMachineIsPersonal = builtins.elem hostname machineRoles.personal;
in
{
home.username = "googlebot";
home.homeDirectory = "/home/googlebot";
home.stateVersion = "24.11";
programs.home-manager.enable = true;
programs.zed-editor = {
enable = thisMachineIsPersonal;
extensions = [
"nix"
"toml"
"html"
"make"
"git-firefly"
"vue"
"scss"
];
userSettings = {
assistant = {
enabled = true;
version = "2";
default_model = {
provider = "openai";
model = "gpt-4-turbo";
};
};
features = {
edit_prediction_provider = "zed";
};
node = {
path = lib.getExe pkgs.nodejs;
npm_path = lib.getExe' pkgs.nodejs "npm";
};
auto_update = false;
terminal = {
blinking = "off";
copy_on_select = false;
};
lsp = {
rust-analyzer = {
# binary = {
# path = lib.getExe pkgs.rust-analyzer;
# };
binary = {
path = "/run/current-system/sw/bin/nix";
arguments = [ "develop" "--command" "rust-analyzer" ];
};
initialization_options = {
cargo = {
features = "all";
};
};
};
};
# tell zed to use direnv and direnv can use a flake.nix enviroment.
load_direnv = "shell_hook";
base_keymap = "VSCode";
theme = {
mode = "system";
light = "One Light";
dark = "Andrometa";
};
ui_font_size = 12;
buffer_font_size = 12;
};
};
}

View File

@ -1,56 +0,0 @@
{ lib, ... }:
with lib;
{
# Passthrough trace for debugging
pTrace = v: traceSeq v v;
# find the total sum of a int list
sum = foldr (x: y: x + y) 0;
# splits a list of length two into two params then they're passed to a func
splitPair = f: pair: f (head pair) (last pair);
# Finds the max value in a list
maxList = foldr max 0;
# Sorts a int list. Greatest value first
sortList = sort (x: y: x > y);
# Cuts a list in half and returns the two parts in a list
cutInHalf = l: [ (take (length l / 2) l) (drop (length l / 2) l) ];
# Splits a list into a list of lists with length cnt
chunksOf = cnt: l:
if length l > 0 then
[ (take cnt l) ] ++ chunksOf cnt (drop cnt l)
else [ ];
# same as intersectLists but takes an array of lists to intersect instead of just two
intersectManyLists = ll: foldr intersectLists (head ll) ll;
# converts a boolean to a int (c style)
boolToInt = b: if b then 1 else 0;
# drops the last element of a list
dropLast = l: take (length l - 1) l;
# transposes a matrix
transpose = ll:
let
outerSize = length ll;
innerSize = length (elemAt ll 0);
in
genList (i: genList (j: elemAt (elemAt ll j) i) outerSize) innerSize;
# attriset recursiveUpdate but for a list of attrisets
combineAttrs = foldl recursiveUpdate { };
# visits every single attriset element of an attriset recursively
# and accumulates the result of every visit in a flat list
recurisveVisitAttrs = f: set:
let
visitor = n: v:
if isAttrs v then [ (f n v) ] ++ recurisveVisitAttrs f v
else [ (f n v) ];
in
concatLists (map (name: visitor name set.${name}) (attrNames set));
# merges two lists of the same size (similar to map but both lists are inputs per iteration)
mergeLists = f: a: imap0 (i: f (elemAt a i));
map2D = f: ll:
let
outerSize = length ll;
innerSize = length (elemAt ll 0);
getElem = x: y: elemAt (elemAt ll y) x;
in
genList (y: genList (x: f x y (getElem x y)) innerSize) outerSize;
}

View File

@ -0,0 +1,28 @@
{ config, ... }:
{
imports = [
../../common/common.nix
];
# NixOS wants to enable GRUB by default
boot.loader.grub.enable = false;
# Enables the generation of /boot/extlinux/extlinux.conf
boot.loader.generic-extlinux-compatible.enable = true;
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXOS_SD";
fsType = "ext4";
};
};
nix.flakes.enable = true;
networking.interfaces.eth0.useDHCP = true;
hardware.deviceTree.enable = true;
hardware.deviceTree.overlays = [
./sopine-baseboard-ethernet.dtbo # fix pine64 clusterboard ethernet
];
}

View File

@ -0,0 +1,9 @@
{ config, ... }:
{
imports = [
../common.nix
];
networking.hostName = "n1";
}

Binary file not shown.

View File

@ -0,0 +1,15 @@
/dts-v1/;
/ {
model = "SoPine with baseboard";
compatible = "pine64,sopine-baseboard\0pine64,sopine\0allwinner,sun50i-a64";
fragment@0 {
/* target = <&ethernet@1c30000>; */
target-path = "/soc/ethernet@1c30000";
__overlay__ {
allwinner,tx-delay-ps = <500>;
};
};
};

View File

@ -1,12 +0,0 @@
{ modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/cd-dvd/iso-image.nix")
./minimal.nix
];
isoImage.makeUsbBootable = true;
networking.hostName = "iso";
}

View File

@ -1,48 +0,0 @@
# From https://mdleom.com/blog/2021/03/09/nixos-oracle/#Build-a-kexec-tarball
# Builds a kexec img
{ config, pkgs, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/netboot/netboot.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./minimal.nix
];
networking.hostName = "kexec";
# stripped down version of https://github.com/cleverca22/nix-tests/tree/master/kexec
system.build = rec {
image = pkgs.runCommand "image" { buildInputs = [ pkgs.nukeReferences ]; } ''
mkdir $out
if [ -f ${config.system.build.kernel}/bzImage ]; then
cp ${config.system.build.kernel}/bzImage $out/kernel
else
cp ${config.system.build.kernel}/Image $out/kernel
fi
cp ${config.system.build.netbootRamdisk}/initrd $out/initrd
nuke-refs $out/kernel
'';
kexec_script = pkgs.writeTextFile {
executable = true;
name = "kexec-nixos";
text = ''
#!${pkgs.stdenv.shell}
set -e
${pkgs.kexec-tools}/bin/kexec -l ${image}/kernel --initrd=${image}/initrd --append="init=${builtins.unsafeDiscardStringContext config.system.build.toplevel}/init ${toString config.boot.kernelParams}"
sync
echo "executing kernel, filesystems will be improperly umounted"
${pkgs.kexec-tools}/bin/kexec -e
'';
};
kexec_tarball = pkgs.callPackage (modulesPath + "/../lib/make-system-tarball.nix") {
storeContents = [
{
object = config.system.build.kexec_script;
symlink = "/kexec_nixos";
}
];
contents = [ ];
};
};
}

View File

@ -1,53 +0,0 @@
{ config, pkgs, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/cd-dvd/channel.nix")
../../common/machine-info
../../common/ssh.nix
];
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "e1000" "e1000e" "virtio_pci" "r8169" ];
boot.kernelParams = [
"panic=30"
"boot.panic_on_fail" # reboot the machine upon fatal boot issues
"console=ttyS0,115200" # enable serial console
"console=tty1"
];
boot.kernel.sysctl."vm.overcommit_memory" = "1";
boot.kernelPackages = pkgs.linuxPackages_latest;
system.stateVersion = "21.11";
# hardware.enableAllFirmware = true;
# nixpkgs.config.allowUnfree = true;
environment.systemPackages = with pkgs; [
cryptsetup
btrfs-progs
git
git-lfs
wget
htop
dnsutils
pciutils
usbutils
lm_sensors
];
environment.variables.GC_INITIAL_HEAP_SIZE = "1M";
networking.useDHCP = true;
services.openssh = {
enable = true;
settings = {
KbdInteractiveAuthentication = false;
PasswordAuthentication = false;
};
};
services.getty.autologinUser = "root";
users.users.root.openssh.authorizedKeys.keys = config.machines.ssh.userKeys;
}

View File

@ -1,57 +0,0 @@
{ config, modulesPath, pkgs, lib, ... }:
let
pinecube-uboot = pkgs.buildUBoot {
defconfig = "pinecube_defconfig";
extraMeta.platforms = [ "armv7l-linux" ];
filesToInstall = [ "u-boot-sunxi-with-spl.bin" ];
};
in
{
imports = [
(modulesPath + "/installer/sd-card/sd-image.nix")
./minimal.nix
];
sdImage.populateFirmwareCommands = "";
sdImage.populateRootCommands = ''
mkdir -p ./files/boot
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
'';
sdImage.postBuildCommands = ''
dd if=${pinecube-uboot}/u-boot-sunxi-with-spl.bin of=$img bs=1024 seek=8 conv=notrunc
'';
###
networking.hostName = "pinecube";
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
boot.consoleLogLevel = 7;
# cma is 64M by default which is waay too much and we can't even unpack initrd
boot.kernelParams = [ "console=ttyS0,115200n8" "cma=32M" ];
boot.kernelModules = [ "spi-nor" ]; # Not sure why this doesn't autoload. Provides SPI NOR at /dev/mtd0
boot.extraModulePackages = [ config.boot.kernelPackages.rtl8189es ];
zramSwap.enable = true; # 128MB is not much to work with
sound.enable = true;
environment.systemPackages = with pkgs; [
ffmpeg
(v4l_utils.override { withGUI = false; })
usbutils
];
services.getty.autologinUser = lib.mkForce "googlebot";
users.users.googlebot = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "video" ];
openssh.authorizedKeys.keys = config.machines.ssh.userKeys;
};
networking.wireless.enable = true;
}

View File

@ -1,12 +0,0 @@
{ config, pkgs, lib, ... }:
{
imports = [
./hardware-configuration.nix
];
# don't use remote builders
nix.distributedBuilds = lib.mkForce false;
nix.gc.automatic = lib.mkForce false;
}

View File

@ -1,58 +0,0 @@
{ config, lib, pkgs, modulesPath, nixos-hardware, ... }:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
nixos-hardware.nixosModules.framework-13-7040-amd
];
boot.kernelPackages = pkgs.linuxPackages_latest;
hardware.framework.amd-7040.preventWakeOnAC = true;
services.fwupd.enable = true;
# fingerprint reader has initially shown to be more of a nuisance than a help
# it makes sddm log in fail most of the time and take several minutes to finish
services.fprintd.enable = false;
# boot
boot.loader.systemd-boot.enable = true;
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ "dm-snapshot" ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
# thunderbolt
services.hardware.bolt.enable = true;
# firmware
firmware.x86_64.enable = true;
# disks
remoteLuksUnlock.enable = true;
boot.initrd.luks.devices."enc-pv" = {
device = "/dev/disk/by-uuid/2e4a6960-a6b1-40ee-9c2c-2766eb718d52";
allowDiscards = true;
};
fileSystems."/" =
{
device = "/dev/disk/by-uuid/1f62386c-3243-49f5-b72f-df8fc8f39db8";
fsType = "btrfs";
};
fileSystems."/boot" =
{
device = "/dev/disk/by-uuid/F4D9-C5E8";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices =
[{ device = "/dev/disk/by-uuid/5f65cb11-2649-48fe-9c78-3e325b857c53"; }];
# 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.wlp1s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

View File

@ -1,26 +0,0 @@
{
hostNames = [
"howl"
];
arch = "x86_64-linux";
systemRoles = [
"personal"
];
hostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEQi3q8jU6vRruExAL60J7GFO1gS8HsmXVJuKRT4ljrG";
userKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKPnLt84bKhUgFxjQf10+Htro9Lo1Pabqm8mGalBUniv"
];
deployKeys = [
# TODO
];
remoteUnlock = {
hostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN0N80r0Sl2WlJaUqfxZPkOtYyGumFazkIqq7eq3Gd2o";
onionHost = "ll6yjnkh4psmfwmtkmqoutl4gq4elqzbmjxv4s6gpgoavyi3kwhjvnqd.onion";
};
}

View File

@ -0,0 +1,34 @@
{ config, pkgs, lib, ... }:
{
imports =[
./hardware-configuration.nix
../../common/common.nix
];
# 5synsrjgvfzywruomjsfvfwhhlgxqhyofkzeqt2eisyijvjvebnu2xyd.onion
nix.flakes.enable = true;
bios = {
enable = true;
device = "/dev/sda";
};
luks = {
enable = true;
device.path = "/dev/disk/by-uuid/2f736fba-8a0c-4fb5-8041-c849fb5e1297";
};
services.gitea = {
enable = true;
hostname = "git.neet.dev";
};
networking.hostName = "liza";
networking.interfaces.enp1s0.useDHCP = true;
security.acme.acceptTerms = true;
security.acme.email = "zuckerberg@neet.dev";
}

View File

@ -0,0 +1,36 @@
# 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 =
[ (modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [ "ata_piix" "virtio_pci" "floppy" "sr_mod" "virtio_blk" ];
boot.initrd.kernelModules = [ "dm-snapshot" ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/b90eaf3c-2f91-499a-a066-861e0f4478df";
fsType = "btrfs";
};
fileSystems."/home" =
{ device = "/dev/disk/by-uuid/b90eaf3c-2f91-499a-a066-861e0f4478df";
fsType = "btrfs";
options = [ "subvol=home" ];
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/2b8f6f6d-9358-4d30-8341-7426574e0819";
fsType = "ext3";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/ef7a83db-4b33-41d1-85fc-cff69e480352"; }
];
}

View File

@ -0,0 +1,118 @@
{ config, pkgs, lib, ... }:
{
imports =[
./hardware-configuration.nix
../../common/common.nix
];
# cuxhh3ei2djpgf2zdkboceuhaxavgr3ipu3d7a2swx4giy2wosfxspyd.onion
nix.flakes.enable = true;
bios = {
enable = true;
device = "/dev/vda";
};
luks = {
enable = true;
device.path = "/dev/disk/by-uuid/6dcf23ea-cb5e-4329-a88b-832209918c40";
};
networking.hostName = "mitty";
networking.interfaces.ens3.useDHCP = true;
services.nginx.enable = true;
containers.jellyfin = {
ephemeral = true;
autoStart = true;
bindMounts = {
"/var/lib" = {
hostPath = "/var/lib/";
isReadOnly = false;
};
};
bindMounts = {
"/secret" = {
hostPath = "/secret";
isReadOnly = true;
};
};
enableTun = true;
privateNetwork = true;
hostAddress = "172.16.100.1";
localAddress = "172.16.100.2";
config = {
imports = [ ../../common/common.nix ];
pia.enable = true;
nixpkgs.pkgs = pkgs;
services.radarr.enable = true;
services.radarr.openFirewall = true;
services.bazarr.enable = true;
services.bazarr.openFirewall = true;
services.sonarr.enable = true;
services.sonarr.openFirewall = true;
services.jackett.enable = true;
services.jackett.openFirewall = true;
services.jellyfin.enable = true;
services.jellyfin.openFirewall = true;
services.deluge.enable = true;
services.deluge.web.enable = true;
services.deluge.web.openFirewall = true;
};
};
services.nginx.virtualHosts."radarr.neet.cloud" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://172.16.100.2:7878";
};
};
services.nginx.virtualHosts."sonarr.neet.cloud" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://172.16.100.2:8989";
};
};
services.nginx.virtualHosts."bazarr.neet.cloud" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://172.16.100.2:6767";
};
};
services.nginx.virtualHosts."jellyfin.neet.cloud" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://172.16.100.2:8096";
};
};
services.nginx.virtualHosts."deluge.neet.cloud" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://172.16.100.2:8112";
};
};
services.nginx.virtualHosts."jackett.neet.cloud" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://172.16.100.2:9117";
};
};
networking.nat.enable = true;
networking.nat.internalInterfaces = [ "ve-jellyfin" ];
networking.nat.externalInterface = "ens3";
security.acme.acceptTerms = true;
security.acme.email = "letsencrypt+5@tar.ninja";
}

View File

@ -0,0 +1,37 @@
# 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 =
[ (modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
boot.initrd.kernelModules = [ "dm-snapshot" ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/mapper/vg-root";
fsType = "btrfs";
options = [ "subvol=root" ];
};
fileSystems."/home" =
{ device = "/dev/mapper/vg-root";
fsType = "btrfs";
options = [ "subvol=home" ];
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/78f9b9a3-40f6-4c6c-a599-5d5067ffa214";
fsType = "ext3";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/26252a81-8a98-45d0-8507-494ecb3901e7"; }
];
}

View File

@ -0,0 +1,513 @@
listen:
hostname: 'localhost'
port: 9000
# Correspond to your reverse proxy server_name/listen configuration (i.e., your public PeerTube instance URL)
webserver:
https: true
hostname: 'mitty.neet.dev'
port: 443
rates_limit:
api:
# 50 attempts in 10 seconds
window: 10 seconds
max: 50
login:
# 15 attempts in 5 min
window: 5 minutes
max: 15
signup:
# 2 attempts in 5 min (only succeeded attempts are taken into account)
window: 5 minutes
max: 2
ask_send_email:
# 3 attempts in 5 min
window: 5 minutes
max: 3
# Proxies to trust to get real client IP
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
# If you run PeerTube behind a remote proxy, add the proxy IP address (or subnet)
trust_proxy:
- 'loopback'
# Your database name will be database.name OR "peertube"+database.suffix
database:
hostname: 'localhost'
port: 5432
ssl: false
suffix: '_prod'
username: 'peertube'
password: 'peertube'
pool:
max: 5
# Redis server for short time storage
# You can also specify a 'socket' path to a unix socket but first need to
# comment out hostname and port
redis:
hostname: 'localhost'
port: 6379
auth: null
db: 0
# SMTP server to send emails
smtp:
# smtp or sendmail
transport: smtp
# Path to sendmail command. Required if you use sendmail transport
sendmail: null
hostname: null
port: 465 # If you use StartTLS: 587
username: null
password: null
tls: true # If you use StartTLS: false
disable_starttls: false
ca_file: null # Used for self signed certificates
from_address: 'admin@example.com'
email:
body:
signature: "PeerTube"
subject:
prefix: "[PeerTube]"
# From the project root directory
storage:
tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before processing...
avatars: '/var/www/peertube/storage/avatars/'
videos: '/var/www/peertube/storage/videos/'
streaming_playlists: '/var/www/peertube/storage/streaming-playlists/'
redundancy: '/var/www/peertube/storage/redundancy/'
logs: '/var/www/peertube/storage/logs/'
previews: '/var/www/peertube/storage/previews/'
thumbnails: '/var/www/peertube/storage/thumbnails/'
torrents: '/var/www/peertube/storage/torrents/'
captions: '/var/www/peertube/storage/captions/'
cache: '/var/www/peertube/storage/cache/'
plugins: '/var/www/peertube/storage/plugins/'
# Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
# Could contain for example assets/images/favicon.png
# If the file exists, peertube will serve it
# If not, peertube will fallback to the default fil
client_overrides: '/var/www/peertube/storage/client-overrides/'
log:
level: 'info' # debug/info/warning/error
rotation:
enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
maxFileSize: 12MB
maxFiles: 20
anonymizeIP: false
log_ping_requests: true
prettify_sql: false
trending:
videos:
interval_days: 7 # Compute trending videos for the last x days
algorithms:
enabled:
- 'best' # adaptation of Reddit's 'Best' algorithm (Hot minus History)
- 'hot' # adaptation of Reddit's 'Hot' algorithm
- 'most-viewed' # default, used initially by PeerTube as the trending page
- 'most-liked'
default: 'most-viewed'
# Cache remote videos on your server, to help other instances to broadcast the video
# You can define multiple caches using different sizes/strategies
# Once you have defined your strategies, choose which instances you want to cache in admin -> manage follows -> following
redundancy:
videos:
check_interval: '1 hour' # How often you want to check new videos to cache
strategies: # Just uncomment strategies you want
# -
# size: '10GB'
# # Minimum time the video must remain in the cache. Only accept values > 10 hours (to not overload remote instances)
# min_lifetime: '48 hours'
# strategy: 'most-views' # Cache videos that have the most views
# -
# size: '10GB'
# # Minimum time the video must remain in the cache. Only accept values > 10 hours (to not overload remote instances)
# min_lifetime: '48 hours'
# strategy: 'trending' # Cache trending videos
# -
# size: '10GB'
# # Minimum time the video must remain in the cache. Only accept values > 10 hours (to not overload remote instances)
# min_lifetime: '48 hours'
# strategy: 'recently-added' # Cache recently added videos
# min_views: 10 # Having at least x views
# Other instances that duplicate your content
remote_redundancy:
videos:
# 'nobody': Do not accept remote redundancies
# 'anybody': Accept remote redundancies from anybody
# 'followings': Accept redundancies from instance followings
accept_from: 'anybody'
csp:
enabled: false
report_only: true # CSP directives are still being tested, so disable the report only mode at your own risk!
report_uri:
security:
# Set the X-Frame-Options header to help to mitigate clickjacking attacks
frameguard:
enabled: true
tracker:
# If you disable the tracker, you disable the P2P aspect of PeerTube
enabled: true
# Only handle requests on your videos.
# If you set this to false it means you have a public tracker.
# Then, it is possible that clients overload your instance with external torrents
private: true
# Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
reject_too_many_announces: false
history:
videos:
# If you want to limit users videos history
# -1 means there is no limitations
# Other values could be '6 months' or '30 days' etc (PeerTube will periodically delete old entries from database)
max_age: -1
views:
videos:
# PeerTube creates a database entry every hour for each video to track views over a period of time
# This is used in particular by the Trending page
# PeerTube could remove old remote video views if you want to reduce your database size (video view counter will not be altered)
# -1 means no cleanup
# Other values could be '6 months' or '30 days' etc (PeerTube will periodically delete old entries from database)
remote:
max_age: '30 days'
plugins:
# The website PeerTube will ask for available PeerTube plugins and themes
# This is an unmoderated plugin index, so only install plugins/themes you trust
index:
enabled: true
check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions
url: 'https://packages.joinpeertube.org'
federation:
videos:
federate_unlisted: false
# Add a weekly job that cleans up remote AP interactions on local videos (shares, rates and comments)
# It removes objects that do not exist anymore, and potentially fix their URLs
# This setting is opt-in because due to an old bug in PeerTube, remote rates sent by instance before PeerTube 3.0 will be deleted
# We still suggest you to enable this setting even if your users will loose most of their video's likes/dislikes
cleanup_remote_interactions: false
peertube:
check_latest_version:
# Check and notify admins of new PeerTube versions
enabled: true
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
url: 'https://joinpeertube.org/api/v1/versions.json'
###############################################################################
#
# From this point, all the following keys can be overridden by the web interface
# (local-production.json file). If you need to change some values, prefer to
# use the web interface because the configuration will be automatically
# reloaded without any need to restart PeerTube.
#
# /!\ If you already have a local-production.json file, the modification of the
# following keys will have no effect /!\.
#
###############################################################################
cache:
previews:
size: 500 # Max number of previews you want to cache
captions:
size: 500 # Max number of video captions/subtitles you want to cache
torrents:
size: 500 # Max number of video torrents you want to cache
admin:
# Used to generate the root user at first startup
# And to receive emails from the contact form
email: 'googlebot@tar.ninja'
contact_form:
enabled: true
signup:
enabled: false
limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited
requires_email_verification: false
filters:
cidr: # You can specify CIDR ranges to whitelist (empty = no filtering) or blacklist
whitelist: []
blacklist: []
user:
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
# -1 == unlimited
video_quota: -1
video_quota_daily: -1
# If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag
# In addition, if some resolutions are enabled the mp4 video file will be transcoded to these new resolutions.
# Please, do not disable transcoding since many uploaded videos will not work
transcoding:
enabled: true
# Allow your users to upload .mkv, .mov, .avi, .wmv, .flv, .f4v, .3g2, .3gp, .mts, m2ts, .mxf, .nut videos
allow_additional_extensions: true
# If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
allow_audio_files: true
# Amount of threads used by ffmpeg for 1 transcoding job
threads: 1
# Amount of transcoding jobs to execute in parallel
concurrency: 1
# Choose the transcoding profile
# New profiles can be added by plugins
# Available in core PeerTube: 'default'
profile: 'default'
resolutions: # Only created if the original video has a higher resolution, uses more storage!
0p: false # audio-only (creates mp4 without video stream, always created when enabled)
240p: false
360p: false
480p: false
720p: false
1080p: false
1440p: false
2160p: false
# Generate videos in a WebTorrent format (what we do since the first PeerTube release)
# If you also enabled the hls format, it will multiply videos storage by 2
# If disabled, breaks federation with PeerTube instances < 2.1
webtorrent:
enabled: false
# /!\ Requires ffmpeg >= 4.1
# Generate HLS playlists and fragmented MP4 files. Better playback than with WebTorrent:
# * Resolution change is smoother
# * Faster playback in particular with long videos
# * More stable playback (less bugs/infinite loading)
# If you also enabled the webtorrent format, it will multiply videos storage by 2
hls:
enabled: true
live:
enabled: true
# Limit lives duration
# -1 == unlimited
max_duration: -1 # For example: '5 hours'
# Limit max number of live videos created on your instance
# -1 == unlimited
max_instance_lives: 20
# Limit max number of live videos created by a user on your instance
# -1 == unlimited
max_user_lives: 3
# Allow your users to save a replay of their live
# PeerTube will transcode segments in a video file
# If the user daily/total quota is reached, PeerTube will stop the live
# /!\ transcoding.enabled (and not live.transcoding.enabled) has to be true to create a replay
allow_replay: true
# Your firewall should accept traffic from this port in TCP if you enable live
rtmp:
port: 1935
# Allow to transcode the live streaming in multiple live resolutions
transcoding:
enabled: true
threads: 2
# Choose the transcoding profile
# New profiles can be added by plugins
# Available in core PeerTube: 'default'
profile: 'default'
resolutions:
240p: false
360p: false
480p: false
720p: false
1080p: false
1440p: false
2160p: false
import:
# Add ability for your users to import remote videos (from YouTube, torrent...)
videos:
# Amount of import jobs to execute in parallel
concurrency: 1
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
enabled: false
# IPv6 is very strongly rate-limited on most sites supported by youtube-dl
force_ipv4: false
# You can use an HTTP/HTTPS/SOCKS proxy with youtube-dl
proxy:
enabled: false
url: ""
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
enabled: false
auto_blacklist:
# New videos automatically blacklisted so moderators can review before publishing
videos:
of_users:
enabled: false
# Instance settings
instance:
name: 'PeerTube'
short_description: 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
description: 'Welcome to this PeerTube instance!' # Support markdown
terms: 'No terms for now.' # Support markdown
code_of_conduct: '' # Supports markdown
# Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc
moderation_information: '' # Supports markdown
# Why did you create this instance?
creation_reason: '' # Supports Markdown
# Who is behind the instance? A single person? A non profit?
administrator: '' # Supports Markdown
# How long do you plan to maintain this instance?
maintenance_lifetime: '' # Supports Markdown
# How will you pay the PeerTube instance server? With your own funds? With users donations? Advertising?
business_model: '' # Supports Markdown
# If you want to explain on what type of hardware your PeerTube instance runs
# Example: "2 vCore, 2GB RAM..."
hardware_information: '' # Supports Markdown
# What are the main languages of your instance? To interact with your users for example
# Uncomment or add the languages you want
# List of supported languages: https://peertube.cpy.re/api/v1/videos/languages
languages:
# - en
# - es
# - fr
# You can specify the main categories of your instance (dedicated to music, gaming or politics etc)
# Uncomment or add the category ids you want
# List of supported categories: https://peertube.cpy.re/api/v1/videos/categories
categories:
# - 1 # Music
# - 2 # Films
# - 3 # Vehicles
# - 4 # Art
# - 5 # Sports
# - 6 # Travels
# - 7 # Gaming
# - 8 # People
# - 9 # Comedy
# - 10 # Entertainment
# - 11 # News & Politics
# - 12 # How To
# - 13 # Education
# - 14 # Activism
# - 15 # Science & Technology
# - 16 # Animals
# - 17 # Kids
# - 18 # Food
default_client_route: '/videos/trending'
# Whether or not the instance is dedicated to NSFW content
# Enabling it will allow other administrators to know that you are mainly federating sensitive content
# Moreover, the NSFW checkbox on video upload will be automatically checked by default
is_nsfw: false
# By default, "do_not_list" or "blur" or "display" NSFW videos
# Could be overridden per user with a setting
default_nsfw_policy: 'do_not_list'
customizations:
javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
# Robot.txt rules. To disallow robots to crawl your instance and disallow indexation of your site, add '/' to "Disallow:'
robots: |
User-agent: *
Disallow:
# Security.txt rules. To discourage researchers from testing your instance and disable security.txt integration, set this to an empty string.
securitytxt:
"# If you would like to report a security issue\n# you may report it to:\nContact: https://github.com/Chocobozzz/PeerTube/blob/develop/SECURITY.md\nContact: mailto:"
services:
# Cards configuration to format video in Twitter
twitter:
username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published
# If true, a video player will be embedded in the Twitter feed on PeerTube video share
# If false, we use an image link card that will redirect on your PeerTube instance
# Change it to "true", and then test on https://cards-dev.twitter.com/validator to see if you are whitelisted
whitelisted: false
followers:
instance:
# Allow or not other instances to follow yours
enabled: true
# Whether or not an administrator must manually validate a new follower
manual_approval: false
followings:
instance:
# If you want to automatically follow back new instance followers
# If this option is enabled, use the mute feature instead of deleting followings
# /!\ Don't enable this if you don't have a reactive moderation team /!\
auto_follow_back:
enabled: false
# If you want to automatically follow instances of the public index
# If this option is enabled, use the mute feature instead of deleting followings
# /!\ Don't enable this if you don't have a reactive moderation team /!\
auto_follow_index:
enabled: false
# Host your own using https://framagit.org/framasoft/peertube/instances-peertube#peertube-auto-follow
index_url: ''
theme:
default: 'default'
broadcast_message:
enabled: false
message: '' # Support markdown
level: 'info' # 'info' | 'warning' | 'error'
dismissable: false
search:
# Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
# If enabled, the associated group will be able to "escape" from the instance follows
# That means they will be able to follow channels, watch videos, list videos of non followed instances
remote_uri:
users: true
anonymous: false
# Use a third party index instead of your local index, only for search results
# Useful to discover content outside of your instance
# If you enable search_index, you must enable remote_uri search for users
# If you do not enable remote_uri search for anonymous user, your instance will redirect the user on the origin instance
# instead of loading the video locally
search_index:
enabled: false
# URL of the search index, that should use the same search API and routes
# than PeerTube: https://docs.joinpeertube.org/api-rest-reference.html
# You should deploy your own with https://framagit.org/framasoft/peertube/search-index,
# and can use https://search.joinpeertube.org/ for tests, but keep in mind the latter is an unmoderated search index
url: ''
# You can disable local search, so users only use the search index
disable_local_search: false
# If you did not disable local search, you can decide to use the search index by default
is_default_search: false

View File

@ -0,0 +1,43 @@
{ config, pkgs, lib, ... }:
{
imports =[
./hardware-configuration.nix
../../common/common.nix
];
# uxzq63kr2uuwutpaqjna2sg4gnk3p65e5bkvedzx5dsxx2mvxhjm7fid.onion
nix.flakes.enable = true;
bios = {
enable = true;
device = "/dev/vda";
};
luks = {
enable = true;
device.path = "/dev/disk/by-uuid/e57ac752-bd99-421f-a3b9-0cfa9608a54e";
};
networking.hostName = "nanachi";
networking.interfaces.ens3.useDHCP = true;
services.icecast = {
enable = true;
hostname = "nanachi.neet.dev";
mount = "stream.mp3";
fallback = "fallback.mp3";
};
security.acme.acceptTerms = true;
security.acme.email = "letsencrypt+5@tar.ninja";
services.nginx.enable = true;
services.nginx.virtualHosts."nanachi.neet.dev" = {
enableACME = true;
forceSSL = true;
root = "/var/www/tmp";
};
}

View File

@ -0,0 +1,37 @@
# 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 =
[ (modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
boot.initrd.kernelModules = [ "dm-snapshot" ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/mapper/vg-root";
fsType = "btrfs";
options = [ "subvol=root" ];
};
fileSystems."/home" =
{ device = "/dev/mapper/vg-root";
fsType = "btrfs";
options = [ "subvol=home" ];
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/47b13d6a-0bbb-42a6-abd9-2b06f76d9718";
fsType = "ext3";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/b7533a50-6dd2-4a64-953b-a8218e77d9fa"; }
];
}

Some files were not shown because too many files have changed in this diff Show More