1
1
Fork 0

Compare commits

..

1 Commits

Author SHA1 Message Date
Jaculabilis 68b58053d4 Add guesshtbook config 2023-06-09 20:26:19 +00:00
35 changed files with 512 additions and 2010 deletions

View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"lastModified": 1668681692,
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
"type": "github"
},
"original": {
@ -31,79 +31,12 @@
"type": "github"
}
},
"intake": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"nixos-shell": "nixos-shell",
"nixpkgs": [
"nixpkgs-2305"
]
},
"locked": {
"lastModified": 1696904526,
"narHash": "sha256-VbPMkzZBNhRB5fj2cEBnTRP1LRJHOWX1d8KGuAqtezQ=",
"ref": "refs/heads/master",
"rev": "8fd6f3b751fd46c0d4571d1cf4f330dc38b8270e",
"revCount": 79,
"type": "git",
"url": "ssh://gitea@git.alogoulogoi.com/Jaculabilis/intake.git"
},
"original": {
"type": "git",
"url": "ssh://gitea@git.alogoulogoi.com/Jaculabilis/intake.git"
}
},
"intake-sources": {
"inputs": {
"nixpkgs": [
"nixpkgs-2305"
]
},
"locked": {
"lastModified": 1700115142,
"narHash": "sha256-EuLKiCD8AVQUEADh+TJoePc6GR+TKVvmbOb4Duld2gI=",
"ref": "refs/heads/master",
"rev": "327a79479d80dc7c1760f32887b7f07723529d66",
"revCount": 26,
"type": "git",
"url": "ssh://gitea@git.alogoulogoi.com/Jaculabilis/intake-sources.git"
},
"original": {
"type": "git",
"url": "ssh://gitea@git.alogoulogoi.com/Jaculabilis/intake-sources.git"
}
},
"nixos-shell": {
"inputs": {
"nixpkgs": [
"intake",
"nixpkgs"
]
},
"locked": {
"lastModified": 1686216465,
"narHash": "sha256-0A4K6xVIyxUi2YZu4+156WwzAO1GDWGcKiMvsXpBQDQ=",
"owner": "Mic92",
"repo": "nixos-shell",
"rev": "65489e7eeef8eeea43e1e4218ad1b99d58852c7c",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "nixos-shell",
"type": "github"
}
},
"nixos-wsl": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs-2305"
"nixpkgs"
]
},
"locked": {
@ -120,46 +53,26 @@
"type": "github"
}
},
"nixpkgs-2305": {
"nixpkgs": {
"locked": {
"lastModified": 1685566663,
"narHash": "sha256-btHN1czJ6rzteeCuE/PNrdssqYD2nIA4w48miQAFloM=",
"lastModified": 1669833724,
"narHash": "sha256-/HEZNyGbnQecrgJnfE8d0WC5c1xuPSD2LUpB6YXlg4c=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4ecab3273592f27479a583fb6d975d4aba3486fe",
"rev": "4d2b37a84fad1091b9de401eb450aae66f1a741e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-2311": {
"locked": {
"lastModified": 1701282334,
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "23.11",
"ref": "refs/tags/22.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"intake": "intake",
"intake-sources": "intake-sources",
"nixos-wsl": "nixos-wsl",
"nixpkgs-2305": "nixpkgs-2305",
"nixpkgs-2311": "nixpkgs-2311"
"nixpkgs": "nixpkgs"
}
}
},

View File

@ -1,103 +1,27 @@
{
inputs = {
nixpkgs-2305.url = "github:NixOS/nixpkgs/23.05";
nixpkgs-2311.url = "github:NixOS/nixpkgs/23.11";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
nixos-wsl = {
url = "github:nix-community/NixOS-WSL";
inputs.nixpkgs.follows = "nixpkgs-2305";
inputs.flake-compat.follows = "flake-compat";
};
intake = {
url = "git+ssh://gitea@git.alogoulogoi.com/Jaculabilis/intake.git";
inputs.nixpkgs.follows = "nixpkgs-2305";
inputs.flake-compat.follows = "flake-compat";
};
intake-sources = {
url = "git+ssh://gitea@git.alogoulogoi.com/Jaculabilis/intake-sources.git";
inputs.nixpkgs.follows = "nixpkgs-2305";
};
nixpkgs.url = "github:NixOS/nixpkgs?ref=refs/tags/22.11";
nixos-wsl.url = "github:nix-community/NixOS-WSL";
nixos-wsl.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = {
self,
nixpkgs-2305,
nixpkgs-2311,
flake-compat,
nixos-wsl,
intake,
intake-sources,
}@inputs: {
nixosModules.beatific = import ./modules/beatific.nix;
nixosConfigurations = let
pinNixpkgs = nixpkgs: { ... }: {
nix.registry.nixpkgs.to = { type = "path"; path = nixpkgs; };
};
in {
backyard = nixpkgs-2311.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.beatific
(pinNixpkgs nixpkgs-2311)
./machine/backyard
];
};
catacomb = nixpkgs-2305.lib.nixosSystem {
outputs = { self, nixpkgs, nixos-wsl }: {
nixosConfigurations = {
catacomb = nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
modules = [
self.nixosModules.beatific
(pinNixpkgs nixpkgs-2305)
./machine/catacomb
];
modules = [ ./machine/catacomb ];
};
centroid = nixpkgs-2311.lib.nixosSystem {
empyrean = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.beatific
(pinNixpkgs nixpkgs-2311)
./machine/centroid
];
modules = [ ./machine/empyrean ];
};
empyrean = nixpkgs-2305.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.beatific
intake.nixosModules.default
intake-sources.nixosModules.default
(pinNixpkgs nixpkgs-2305)
./machine/empyrean
];
};
imperium = nixpkgs-2311.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.beatific
(pinNixpkgs nixpkgs-2311)
({ ... }: {
environment.systemPackages = [ nixpkgs-2311.legacyPackages."i686-linux".gperftools ];
})
./machine/imperium
];
};
stagirite = nixpkgs-2305.lib.nixosSystem {
stagirite = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
nixos-wsl.nixosModules.wsl
./machine/stagirite
];
};
unfolder = nixpkgs-2305.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModules.beatific
(pinNixpkgs nixpkgs-2305)
./machine/unfolder
];
};
};
};
}

View File

@ -1,14 +0,0 @@
{
tvb = [
./nix-on-droid.vagrant.pub
./tvb.backyard.pub
./tvb.catacomb.pub
./tvb.centroid.pub
./tvb.empyrean.pub
./tvb.imperium.pub
./tvb.palamas.pub
./tvb.stagirite.pub
./tvb.unfolder.pub
./tvb.vagrant.pub
];
}

1
keys/monitor.isidore.pub Normal file
View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCzx37PqIaJVWdQs5meVgI4Cg1GRGk6srrix0AUvQeaBS9MOVtJLi+HfnNkcW4GPk9qVlz1E7ciQ7YUw+ny7QbHxcbib2Tawwfk3cx9xiNcN6UI9EzF/rTbNB2Cex8A4sEr5UvoE0BjT5yPaXyrjn8vLksGWq4dlZBM3xeJqe3KP+OxPL0vik3SVGOr/6ZQ7H9cwX5+/p6rCWQuVtwZMcaE6QyXYg5J5FQUaIrFvKbOVklJIkQSbXGzIYQO/QlQC0xWGBapJtGQw/lsQ3YqnFSMkkw8qrKbde07rg8p1FSuqTu+a1ePK+/F4klNel174+39YSobY2/6biPP3Uj/Yhorf4Tw141tIT75e3ebtK5faatQmNOyXcA7LULXK7XemJkZy8PmbNNTeBIKyoI6XQdzk95gpb2C4LEJmV040YMgXhOIiKsHQVgss9FuC+oP5jXWU/JuNXBRHa1IpJjcJhIhg7jnh6ZNZHyK0EpeUs3Zp7usv7mz6CGwb04yvTsWOhZRc+6EoHaHyrNvFPfkPeGsZzxhIbprCyDMtKWsI9GCtztcRhQWBgornUVEs5CurLJBbClQTrZLVv2fn9UnXWPYZTLYL7aFwRIGyKr8ZOOMFxbLOGdeFlqc5TGCP416e8PZvhE+BuDmxiKtQJ/dsQFqVzvOZb2WQRsYRPYvvXbvUw== monitor@isidore

View File

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEpvV9dKUVBlv5VU3LU0XIGTQK/McPf3H31HHD/vINGG nix-on-droid@vagrant

View File

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGYv5vs78+UAeRagopi4BuvmmK1l3zZwQPY3R4rnp7oV tvb@backyard

View File

@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDitOuQeddhGh3Zg/fnawTrIdomMhgR6rJchbfAAEUz7JDWyrwZoU0/kMBcn9qZZBFE0MDeUSbQbF+voA7/dwZKB+3GBOurg75KSxtwDLHw4urC5U9VCZnbOrFnl5K51oopduxE7vqZdtTzJztcDVKwJILSFPS2hDC3Zt7+pnB1z0aMOJIMyqfogSk29dxREHO130tbrRWI6bCrAJKXWMFSEx6N1pGoOvOKZu4YVdiDSqTPdSYW4D4ivC5Wtt74rGz8/su5xvZKHgNf/d/hILlg/zhnGzH5gn8HvN5pqRksh2fiteGxDQoXgaRGEb93RLe6KV7XPy60vmI+uE06lsow0C7hLrSWorhLQOuLG1J5JzzF0SmFRUOYQoc6BbOLLVXVBa1VcmpupHMJiw6ldJv9yD410bXd+IwIDS9IkW1MIfvN5j35AvnaJCEHVUSrkEjgqCpkNxNPl7V3h7YRw6F/AljTvgEER91mPUvwfJrC0sMo/+DWNwIbnK+jjioD2szHXz2EpXlVhkMSwey/rNZffQpzFHWsm3Za/+4KxWBUO/2MRz2x1haDvvD37hN2kC5w0DSC6MsJucDVlyEjgDQkVEBKujkBv4fpYLpN4VoqVDYytpgnRTm0QIb+ISStG19Fq1uj3UtHFyWI5d8P3jiev4DTtqCpQo9NWa4zu/mbCw== tvb@catacomb

View File

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC364peKQVWeqFNzhJJkvHumKerKPkczeX2I/e9FFZyL tvb@centroid

View File

@ -1 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCuAlVTZ4g7x8Fv3YoHn4KRr5GHfylgJjkC6lujti7Fd3JhZ34OB5L/FuCKLo0rv/1qpIcGHk01FMNT/hDcbyBRPN5QG1s7x39Bt8yBVePoJK0+malGkI90z4zV1b5LEjvRhr7+KuOskt3eVbhKcEXiJpDdqOOgqvppmQZOb6jb03+q01rPLzz0S20jUDLRSdnmlcNDv16lMXTX9zgWDeVIVErTvoLAI8RloqKUgienheLJSS6sdEk4AjL2PKldKE6mIOxZWiYSTS9+2ClqV8u3l1UTZ7+DOw8FWASmMYaaBvJRDEUzDKA/iOAjpfPO7WOn+yjRyiT7IFVFoz2XtXwZD0OEQhbgLZtPlhOHh5wn++YNnvzLYd5cYSWVya0FYzVis24hbaGjrwB5tzD0riL2lRzo3ZjktpL7RlCSLfO/2Cb14hLOxZTZZcgdM6Ef/1YatJiEpRQ7FLP45Di3hKc9QEsqQP1BrhhYQOCWwEHAgVXM8W1ncEBXiCnrelegVy0= tvb@empyrean
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC1h/6CedtyFIjFTT7M5e0dEKJkhPbCQ1RLhtj/83H//AK6bzRbWfnKzSH0VXpsHjaU/Ipc1Jl2fQ23r8i/QyKHUKK7xPeGcY5J7oIHVggkEKW2n9R27Ml9BriKxx6ltButnwSOVYk7dyNhz93GYTTEF3ydh4X7HP2Cv7rh5fbca3s69GXRjOVGmIuuVbaJiaMmiWhBI4LYtqwpo1zGXMjfNlXcMFw7VA4EG2UpkJkcWeyN/AI3ZS/YybcTQNCAg+Xnk94CVys/b5wTuGWRplDrRlNR00MUWpr94+u/xk+sC4/6vT7je2W4GjA1jTq4XmewTEV4cxOu+l1jtHZBgvHho5X0fOogdN52MkjuaXzByZB/Br5xTkBRv0tDf5haX4HzuBC29yU9rt62SEQRzvZxObCCGDcIYR/1HTgjDZkRuwgO/1RoRh5SvwbptvPwpBMvx25S//ZJESLO++lqxjgRZSr/05YpZ5diffEIGd1J0Jz0NGv4XBPcMBnSMKFmdwOy+5jnBbW2OjPQaPslXHEKYTfOByzCA8LgppkxvmL08GIjxYfEljioHR5by+Z6SjG1VvifMFA6FN/+DN6ZH0LmeOMa5YM1Zl3bx5RaANpnSNvi6rmxkQa1oMaK0l482FvpVkg6o1h9si/AeNTEbiX9ozHdjekfuzBSk6Jfo6xw+Q== tvb@empyrean

View File

@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHGUvJ7Gd1mN2mF/hy12dizwEw64zn+jT0vFpkhGF07s tvb@imperium

View File

@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCom6c/UrhM6KrzDcm8kJGZJ5ugR4wRfsFUhTJ5F4oLNQcNspvqlUfxT4Hjv4xHA5hCjvPYi7JxVWxBsOEmmIs/LAjFXRNw9Kz8m7TCf/lct+JUpf6eCn6a/orhA0OdUv4c9aiH2Dzp4ob3lR6xYvYUOH3LuffqypSXP0k5zCSetROUgTZibEpa0oE2GevcyjQAExpCjx02ZRIGkGOM20CXtm3EJsTzIs6uk4+2F9hB+HMqePFKCzsgOlFfVCNWiKNywLavXo7Gn0O9pWidufXZ9s8nCBiOEzU1n6T8pNQBat4z7G1uKFKKWNDPL6/HNEmCq0JxUuXSSjg8LIk+uqPH49Ubpf8EORON8z8nVFAAqbuI6rdYc7J9DllQ7hb7EBT9w9w+mcz3XH0BsGzJUOMD9r0P96pWOn+I6A9jPmOqoOZDzGpfYS18+zYWET/ohH4U4pABcW4nwGx0Jz7Fj6iQW3tyjOefuXcz+sJoYKo/9FOh44KYf4pgu3hfoMlGC0M= tvb@unfolder

View File

@ -1,58 +0,0 @@
{ config, pkgs, ... }:
{
imports = [
./hardware-configuration.nix
./jellyfin.nix
];
boot.loader.grub = {
enable = true;
device = "/dev/sda";
};
beatific.hostName = "backyard";
beatific.defaults.tvbSync = false;
# Enable networking
networking.networkmanager.enable = true;
users.users.tvb.extraGroups = [ "networkmanager" ];
networking.firewall = {
enable = false;
};
#
users.users.guest = {
group = "guest";
description = "gueSSHtbook sshd user";
};
users.groups.guest = {};
systemd.services.guesshd = let
nssModulesPath = config.system.nssModules.path;
in {
enable = false;
description = "gueSSHtbook SSH daemon";
wantedBy = "multi-user.target";
after = [ "network.target" ];
stopIfChanged = false;
path = [ config.programs.ssh.package pkgs.gawk ];
environment.LD_LIBRARY_PATH = nssModulesPath;
#restartTriggers = [ config path goes here ];
# need keys to exist, see prestart in sshd.nix
serviceConfig = {
ExecStart = "${config.programs.ssh.package}/bin/sshd -D -f /path/to/config";
KillMode = "process";
Restart = "always";
Type = "simple";
};
networking.firewall.allowedTCPPorts = [ 2345 ];
#
# This value governs how some stateful data, like databases, are handled
# across different versions of NixOS. This should not be changed to a new
# release unless the sysadmin has determined that no services would be
# adversely affected by changing this.
system.stateVersion = "23.05";
}

View File

@ -1,35 +0,0 @@
# 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 + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/798089ca-f249-431d-aa08-65909b85a184";
fsType = "ext4";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/5d55f9b7-cde3-403d-9c91-61c4b68c71f9"; }
];
# 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.eno1.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@ -1,39 +0,0 @@
{ pkgs, ... }:
{
# Enable jellyfin
services.jellyfin.enable = true;
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts = {
# Create a default vhost to deny traffic, so traffic has to actually match a vhost
default = {
default = true;
locations."/".return = "444";
};
"jellyfin" = {
listen = [
# Available on the local network (host managed by router)
{ addr = "jellyfin.backyard.lan"; port = 80; }
# Available by name on beatific (host managed by beatific module)
{ addr = "jellyfin.backyard.home"; port = 80; }
# Available by port on beatific until I solve DNS for non-NixOS hosts
{ addr = "10.22.20.8"; port = 8096; }
];
locations."/".extraConfig = ''
proxy_buffering off;
proxy_pass http://localhost:8096/;
'';
};
};
};
networking.firewall.allowedTCPPorts = [
80 # http
8096 # jellyfin
];
users.users.tvb.extraGroups = [ "jellyfin" ];
}

View File

@ -1,15 +1,13 @@
{ pkgs, lib, ... }:
{
let
beatific = import ../../modules/beatific.nix;
in {
imports = [
./hardware-configuration.nix
./fileserver.nix
./mirror.nix
];
beatific.hostName = "catacomb";
beatific.defaults.tvbSync = true;
boot = {
loader = {
# Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
@ -19,26 +17,42 @@
};
supportedFilesystems = ["zfs"];
zfs.enableUnstable = true;
kernelParams = [ "zfs.zfs_dmu_offset_next_sync=0" ];
};
system.stateVersion = "22.11"; # Read the usual warning
swapDevices = [ { device = "/swap"; size = 1024; } ];
environment.systemPackages = with pkgs; [
lsof # list open files
mkpasswd # used for setting SMB passwords, I think?
samba # provides smbpasswd, mostly
smartmontools # provides smartctl
usbutils # provides lsusb
console.keyMap = "us";
i18n.defaultLocale = "en_US.UTF-8";
environment.systemPackages = with pkgs;
let
py3-packages = python-packages: with python-packages; [
flask
];
py3-with-packages = python3.withPackages py3-packages;
in [
wget vim curl git htop bash tmux psmisc man-pages pv lsof
zip unzip
py3-with-packages
usbutils
hdparm sdparm smartmontools gptfdisk gnufdisk
dosfstools
mkpasswd samba
tinc_pre
#file-rename
rsync
rclone gnupg
];
networking = {
hostId = "beeeeee5"; # this must be consistent for ZFS
hostName = "catacomb";
hostId = "beeeeee5";
firewall = {
enable = true;
allowedTCPPorts = [ 139 445 ];
allowPing = true;
allowedTCPPorts = [ 22 139 445 ];
allowedUDPPorts = [ 137 138 ];
};
};
@ -58,7 +72,15 @@
];
};
services.openssh.settings.PasswordAuthentication = true;
services.openssh = {
enable = true;
passwordAuthentication = true;
};
services.ntp = {
enable = true;
servers = ["time.nist.gov"];
};
services.rsyncd.enable = true;
@ -140,6 +162,22 @@
};
};
services.nebula.networks.beatific = lib.recursiveUpdate beatific.nebula-defaults {
enable = true;
# Network certificate and host credentials
ca = "/etc/nebula/beatific/beatific.crt";
cert = "/etc/nebula/beatific/catacomb.crt";
key = "/etc/nebula/beatific/catacomb.key";
# Connect to the lighthouse at empyrean
# Note that this is a VPN address, not a public address
lighthouses = [ beatific.empyrean-vpn-ip ];
# Map the lighthouse address to its public address
staticHostMap = beatific.empyrean-host-map;
};
services.zfs = {
autoScrub = {
enable = true;
@ -153,14 +191,19 @@
};
users.users.tvb = {
isNormalUser = true;
uid = 1001;
extraGroups = [ "nas" ];
packages = [
(pkgs.writeShellScriptBin "yt-dlp" ''
exec $HOME/.env/bin/yt-dlp "$@"
'')
password = "badpassword";
extraGroups = ["wheel" "nas"];
openssh.authorizedKeys.keyFiles = [
../../keys/tvb.palamas.pub
../../keys/tvb.stagirite.pub
../../keys/tvb.vagrant.pub
../../keys/monitor.isidore.pub
../../keys/inquisitor.conduit.pub
];
};
#./keys/tvb.empyrean.pub
users.users.katydid = {
isNormalUser = true;
@ -168,4 +211,5 @@
};
nix.settings.cores = 4;
nix.extraOptions = "experimental-features = nix-command flakes";
}

View File

@ -6,7 +6,7 @@ let
catacombServerSource = builtins.fetchGit {
url = "https://git.alogoulogoi.com/Jaculabilis/catacomb-server.git";
ref = "develop-nix";
rev = "3d6fb16948c377f94d030648849f120c8ada3884";
rev = "63574bb39cc777deb56a76548f08789d238fcfec";
};
catacombServer = pkgs.callPackage catacombServerSource {};

View File

@ -18,11 +18,6 @@
fsType = "ext4";
};
fileSystems."/backup" =
{ device = "catapool/backup";
fsType = "zfs";
};
fileSystems."/home/katydid" =
{ device = "catapool/user/katydid";
fsType = "zfs";
@ -33,6 +28,41 @@
fsType = "zfs";
};
fileSystems."/backup" =
{ device = "catapool/backup";
fsType = "zfs";
};
fileSystems."/home/katydid/audio" =
{ device = "catapool/user/katydid/audio";
fsType = "zfs";
};
fileSystems."/home/katydid/doc" =
{ device = "catapool/user/katydid/doc";
fsType = "zfs";
};
fileSystems."/home/katydid/image" =
{ device = "catapool/user/katydid/image";
fsType = "zfs";
};
fileSystems."/home/katydid/video" =
{ device = "catapool/user/katydid/video";
fsType = "zfs";
};
fileSystems."/nas/video" =
{ device = "catapool/nas/video";
fsType = "zfs";
};
fileSystems."/nas/doc" =
{ device = "catapool/nas/doc";
fsType = "zfs";
};
fileSystems."/nas/image" =
{ device = "catapool/nas/image";
fsType = "zfs";
@ -43,21 +73,11 @@
fsType = "zfs";
};
fileSystems."/nas/doc" =
{ device = "catapool/nas/doc";
fsType = "zfs";
};
fileSystems."/nas/game" =
{ device = "catapool/nas/game";
fsType = "zfs";
};
fileSystems."/nas/video" =
{ device = "catapool/nas/video";
fsType = "zfs";
};
swapDevices = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking

View File

@ -1,22 +0,0 @@
{ ... }:
{
# Configure nginx to serve /nas/doc/website/mirror/
services.nginx = {
enable = true;
virtualHosts."mirror.catacomb.home" = {
listen = [
{ addr = "10.22.20.2"; }
# Binding to 10.22.20.2:80 here confuses the empyrean revproxy, even when the revproxy
# is configured with the same hostname, and it serves from fileserver.nix instead.
# This nonstandard port supports the revproxy use case.
{ addr = "10.22.20.2"; port = 7474; }
];
root = "/nas/doc/website/mirror";
};
};
networking.firewall.allowedTCPPorts = [ 7474 ];
users.users.nginx.extraGroups = ["nas"];
}

View File

@ -1,95 +0,0 @@
{ pkgs, ... }:
{
imports = [
./hardware-configuration.nix
];
boot.loader.grub = {
efiSupport = true;
efiInstallAsRemovable = true;
device = "nodev";
};
beatific.hostName = "centroid";
beatific.defaults = {
tvbSync = false;
};
networking.networkmanager.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# To avoid needing an active user session, run a single system instance
systemWide = true;
};
environment.systemPackages = with pkgs; [
mpv # cli media player
];
users.users.tvb.extraGroups = [
"mopidy"
"networkmanager"
"pipewire"
];
users.groups.mopidy = {}; # rw group for media directory
users.users.mopidy.extraGroups = [
"mopidy"
"pipewire" # necessary to allow the system service to play sound
];
services.mopidy = let
mopidy-ytdlp = pkgs.callPackage ./mopidy-youtube.nix { };
in {
enable = true;
extensionPackages = with pkgs; [
mopidy-jellyfin
mopidy-musicbox-webclient
# Replace the default mopidy-youtube, which doesn't have yt-dlp or a way to inject it
mopidy-ytdlp
];
configuration = ''
[file]
media_dirs =
/media/music|Music
[jellyfin]
hostname = jellyfin.backyard.lan
username = mopidy
password = mopidy
libraries = Music,Weird Song Halftime
album_format = {Name} ({ProductionYear})
[youtube]
youtube_dl_package = yt_dlp
'';
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts = {
default = {
default = true;
locations."/".return = "444";
};
"mopidy.home.ktvb.site" = {
listen = [
{ addr = "10.22.20.7"; }
{ addr = "centroid.lan"; }
];
locations."/" = {
proxyWebsockets = true;
proxyPass = "http://localhost:6680";
};
};
};
};
networking.firewall.allowedTCPPorts = [ 80 ];
system.stateVersion = "23.11";
}

View File

@ -1,40 +0,0 @@
# 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 + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/a08e9b3c-8ac7-433e-bf2c-6da234a2fb4a";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/5D60-DD0E";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/6dce2950-f8dd-49eb-b4d3-fdcf06cf920b"; }
];
# 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.eno1.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp0s20f0u1.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@ -1,58 +0,0 @@
{ lib
, fetchFromGitHub
, python3
, mopidy
}:
python3.pkgs.buildPythonApplication rec {
pname = "mopidy-youtube";
version = "3.6";
format = "setuptools";
src = fetchFromGitHub {
owner = "natumbri";
repo = pname;
rev = "refs/tags/v${version}";
hash = "sha256-Mp8eCVNGokJRwmYiZYCYRwV1QVDV02Uqfh6fGcPgJss=";
};
propagatedBuildInputs = with python3.pkgs; [
beautifulsoup4
cachetools
pykka
requests
youtube-dl # this no longer works
yt-dlp # <- added
ytmusicapi
] ++ [
mopidy
];
nativeCheckInputs = with python3.pkgs; [
vcrpy
pytestCheckHook
];
disabledTests = [
# Test requires a YouTube API key
"test_get_default_config"
];
disabledTestPaths = [
# Disable tests which interact with Youtube
"tests/test_api.py"
"tests/test_backend.py"
"tests/test_youtube.py"
];
pythonImportsCheck = [
"mopidy_youtube"
];
meta = with lib; {
description = "Mopidy extension for playing music from YouTube";
homepage = "https://github.com/natumbri/mopidy-youtube";
license = licenses.asl20;
maintainers = with maintainers; [ ];
};
}

View File

@ -9,18 +9,16 @@
[ # Include the results of the hardware scan.
./hardware-configuration.nix
#./amanuensis.nix
#./redstring.nix
./catacomb.nix
./gitea.nix
./sync-pipeline.nix
./inquisitor.nix
];
beatific.hostName = "empyrean";
beatific.isLighthouse = true;
beatific.defaults.tvbSync = true;
# Use the GRUB 2 boot loader.
boot.loader.grub = {
enable = true;
version = 2;
device = "/dev/xvda";
extraConfig = "serial --unit=0 --speed=115200 ; terminal_input serial console ; terminal_output serial console";
};
@ -29,20 +27,42 @@
nix = {
package = pkgs.nixFlakes;
settings.max-jobs = 2;
extraOptions = ''
experimental-features = nix-command flakes
'';
};
swapDevices = [ { device = "/swap"; size = 1024; } ];
networking.hostName = "empyrean";
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
# Per-interface useDHCP will be mandatory in the future, so this generated config
# replicates the default behaviour.
networking.useDHCP = false;
networking.interfaces.eth0.useDHCP = true;
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
console = {
font = "Lat2-Terminus16";
keyMap = "us";
};
# Set your time zone.
time.timeZone = "UTC";
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
tinc_pre
vim htop git tinc_pre python3
gitea
];
environment.variables.EDITOR = "vim";
services.nginx = let
static-site = srv-dir: {
@ -54,36 +74,26 @@
index index.html;
'';
};
service-stub = {
rejectSSL = true;
locations."/".return = "403";
};
in {
enable = true;
recommendedProxySettings = true;
virtualHosts = {
# Static pages
"home.ktvb.site" = static-site "home.ktvb.site";
"wedding.ktvb.site" = static-site "wedding.ktvb.site";
"www.ktvb.site" = static-site "www.ktvb.site";
"www.alogoulogoi.com" = static-site "www.alogoulogoi.com";
"ecumene.alogoulogoi.com" = static-site "ecumene.alogoulogoi.com";
# Home service stub domains
"mopidy.home.ktvb.site" = service-stub;
"jellyfin.home.ktvb.site" = service-stub;
# mirror revproxy
"mirror.alogoulogoi.com" = {
enableACME = true;
forceSSL = true;
"mirror.alogoulogoi.com" = static-site "mirror.alogoulogoi.com";
"www.alogoulogoi.com" = static-site "www";
newtab = {
listen = [ { addr = "10.22.20.1"; port = 80; } ];
root = "/srv/newtab/";
extraConfig = ''
access_log /var/log/nginx/access_mirror.alogoulogoi.com.log;
access_log /var/log/nginx/access_newtab.log;
index index.html;
'';
locations."/".proxyPass = "http://mirror.catacomb.home:7474/";
};
# Deny all other subdomains
"alogoulogoi.com" = {
default = true;
rejectSSL = true;
locations."/".return = "444";
};
};
@ -93,9 +103,20 @@
acceptTerms = true;
};
services.gitolite = {
enable = true;
adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDkSvOcY0eFuYqewc73MNHGP/Owzfnt+BZDSOCwr4h+gJenOBmXol695sRKdIw8phgshb6LsNyWeEAr3YISwzjdOvWNKpSsdyH/79VFIffC+/RBhPFhIPHn1zpHIwgthXji8FMmnO6B1bhJvbJxD5bUqhdBLnUeMhNgUkDj4Qi42S8yK1VZkgWRuPOTGqlzkODEdzG6OST7ndL58jEQaK8R2KRC2cZfrjingmPL+ORyf1/lGwyF6MEAmbQuE6UdspMs8FWt2e8jQJS+ZQ8dl+NDFrC1QfPRFpBJWnQceBcfAVZTtDaJpRhc4ClZstBy/bVRjiKeQiNv5NasjKRvvIvot4+LXmBKrXJs81enExtMSHMPuqPRlyVZLMMCVmdLDP/HUYOASDzlUhV/v5Wp0jjY4Wy0IWC7nm7P8EKsp1ZofwU6rJ9XPLpQJt7UUURX71h1FMaqi+lylW6xkD3LqD8oT5Bdp+Vs0bUbPQVRw1Fenjc6G1URU94GOAggyNgsWms= root@empyrean";
};
services.ntp = {
enable = true;
servers = ["time.nist.gov"];
};
services.openssh = {
settings.PasswordAuthentication = false;
settings.PermitRootLogin = "prohibit-password";
enable = true;
passwordAuthentication = false;
permitRootLogin = "prohibit-password";
};
services.tinc.networks.beatific = {
@ -103,15 +124,36 @@
chroot = false;
};
services.intake = {
listen = { addr = "10.22.20.1"; };
users.tvb.enable = true;
users.tvb.extraPackages = [ pkgs.intakeSources pkgs.openssh ];
services.nebula.networks.beatific = {
enable = true;
# Network certificate and host credentials
ca = "/etc/nebula/beatific/beatific.crt";
cert = "/etc/nebula/beatific/empyrean.crt";
key = "/etc/nebula/beatific/empyrean.key";
# This host has a well-known IP at its VPS host, so it can function as a lighthouse/entry node
isLighthouse = true;
# Listen to connection requests from the public Internet
listen.port = 4242;
listen.host = "vpn.alogoulogoi.com";
# Don't filter anything at the VPN level
firewall.outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
firewall.inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
settings = {
# Enable UDP holepunching both ways, which allows nodes to establish more direct connections with each other
punchy = { punch = true; response = true; };
};
};
networking.firewall = {
enable = true;
allowPing = true;
allowedTCPPorts = [
22 # ssh
80 # http
443 # https
655 # tinc
@ -121,13 +163,50 @@
];
};
users.users.tvb = {
isNormalUser = true;
group = "tvb";
extraGroups = [ "wheel" ]; # Enable sudo for the user.
openssh.authorizedKeys.keyFiles = [
../../keys/tvb.palamas.pub
../../keys/tvb.stagirite.pub
];
};
users.groups.tvb = {};
/*
users.users.guestbook = let
sign-guestbook = pkgs.writeShellScriptBin "sign-guestbook" ''
echo "Hello world!"
''
# Hack the shell path in since writeShellScriptBin doesn't take passthru
// { shellPath = "/bin/sign-guestbook"; };
in {
group = "guestbook";
password = "";
description = "Guestbook login";
shell = sign-guestbook;
isNormalUser = true;
createHome = false;
home = "/var/empty";
useDefaultShell = false;
};
users.groups.guestbook = {};
services.openssh.extraConfig = ''
Match user guestbook
PasswordAuthentication yes
PermitEmptyPasswords yes
Match all
PasswordAuthentication no
'';*/
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.05"; # Did you read the comment?
system.stateVersion = "20.03"; # Did you read the comment?
}

View File

@ -5,7 +5,12 @@
{
# Gitea configuration
services.gitea = {
# Enable Gitea and configure for reverse proxy
enable = true;
httpAddress = "127.0.0.1";
httpPort = 3300;
domain = "git.alogoulogoi.com";
rootUrl = "https://git.alogoulogoi.com/";
#useWizard = true; # Needed for first-time building
@ -17,13 +22,6 @@
interval = "weekly";
};
settings = {
"server" = {
# Configuration for reverse proxy
ROOT_URL = "https://git.alogoulogoi.com/";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3300;
DOMAIN = "git.alogoulogoi.com";
};
"repository" = {
DEFAULT_PRIVATE = true;
};
@ -39,13 +37,10 @@
"security" = {
INSTALL_LOCK = true;
};
"session" = {
SESSION_LIFE_TIME = 86400 * 7; # 1 week
};
"picture" = {
DISABLE_GRAVATAR = true;
};
#"cron.archive_cleanup".ENABLED = false; # TODO: figure out why this was enabled
"cron.archive_cleanup".ENABLED = false;
"cron.sync_external_users".ENABLED = false;
log.LEVEL = "Info";
# Private server
@ -71,8 +66,5 @@
proxy_pass http://localhost:3300/;
'';
};
# Give tvb group access to gitea config
users.users.tvb.extraGroups = [ "gitea" ];
}

View File

@ -0,0 +1,138 @@
{pkgs, ...}:
let
# Import the inquisitor package and build it
inquisitorSource = pkgs.fetchFromGitHub {
owner = "Jaculabilis";
repo = "Inquisitor";
rev = "addcb4f1a56c878b58f2e1c38dfd485761b31306";
sha256 = "0jjl92nf1y40acdly1kls0mppcadvmgbfr6qwczxni1f1zphw3as";
};
inquisitor = pkgs.callPackage inquisitorSource { inherit pkgs; };
# Define the inquisitor data directory
inquisiDir = "/var/lib/inquisitor";
# Define an scp helper for executing in cron jobs
scp-helper = pkgs.writeShellScriptBin "scp-helper" ''
${pkgs.openssh}/bin/scp -i ${inquisiDir}/inquisitor.key -oStrictHostKeyChecking=no "$@"
'';
# Define the inquisitor service user
inquisitorUser = {
name = "inquisitor";
group = "inquisitor";
description = "Inquisitor service user";
isSystemUser = true;
shell = pkgs.bashInteractive;
packages = [ inquisitor pkgs.cron ];
};
# Create the inquisitor config file in the nix store, pointing to /var/lib/
inquisitorConfig = pkgs.writeTextFile {
name = "inquisitor.conf";
text = ''
DataPath = ${inquisiDir}/data/
SourcePath = ${inquisiDir}/sources/
CachePath = ${inquisiDir}/cache/
Verbose = false
LogFile = ${inquisiDir}/inquisitor.log
'';
};
# Create a setup script to ensure the service directory state
inquisitorSetup = pkgs.writeShellScriptBin "inquisitor-setup.sh" ''
# Ensure the service directory and the default source directory
${pkgs.coreutils}/bin/mkdir -p ${inquisiDir}/data/inquisitor/
${pkgs.coreutils}/bin/mkdir -p ${inquisiDir}/sources/
${pkgs.coreutils}/bin/mkdir -p ${inquisiDir}/cache/
if [ ! -f ${inquisiDir}/data/inquisitor/state ]; then
${pkgs.coreutils}/bin/echo "{}" > ${inquisiDir}/data/inquisitor/state
fi
# Ensure the service owns the folders
chown -R ${inquisitorUser.name} ${inquisiDir}
# Ensure the scp helper is present
if [ -f ${inquisiDir}/scp-helper ]; then
rm ${inquisiDir}/scp-helper
fi
ln -s -t ${inquisiDir} ${scp-helper}/bin/scp-helper
'';
# Create a run script for the server
inquisitorRun = pkgs.writeShellScriptBin "inquisitor-run.sh" ''
cd ${inquisiDir}
${inquisitor}/bin/gunicorn \
--bind=localhost:24133 \
--workers=4 \
--timeout 120 \
--log-level debug \
"inquisitor.app:wsgi()"
'';
# Create a wrapper to execute the cli as the service user
inquisitorWrapper = pkgs.writeShellScriptBin "inq" ''
sudo --user=inquisitor ${inquisitor}/bin/inquisitor "$@"
'';
in
{
users.users.inquisitor = inquisitorUser;
users.groups.inquisitor = {};
# Link the config in /etc to avoid envvar shenanigans
environment.etc."inquisitor.conf".source = "${inquisitorConfig}";
# Give all users the inq wrapper
environment.systemPackages = [ inquisitorWrapper ];
# Allow the sudo in the cli wrapper without password
security.sudo.extraRules = [{
commands = [{
command = "${inquisitor}/bin/inquisitor";
options = [ "NOPASSWD" ];
}];
runAs = "${inquisitorUser.name}";
groups = [ "users" ];
}];
# Run the setup script on activation
system.activationScripts.inquisitorSetup = "${inquisitorSetup}/bin/inquisitor-setup.sh";
# Set up the inquisitor service
systemd.services.inquisitor =
{
description = "Inquisitor server";
script = "${inquisitorRun}/bin/inquisitor-run.sh";
serviceConfig = {
User = "${inquisitorUser.name}";
Type = "simple";
};
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
enable = true;
};
# Set up nginx to reverse proxy from the beatific url to the inq server
services.nginx.enable = true;
services.nginx.virtualHosts.inquisitorHost = {
listen = [ { addr = "10.22.20.1"; port = 80; } ];
locations."/".extraConfig = ''
access_log /var/log/nginx/access.inquisitor.log;
proxy_buffering off;
proxy_pass http://localhost:24133/;
'';
};
# Allow nginx through the firewall
networking.firewall = {
allowedTCPPorts = [
80 # http
443 # https
];
};
# Enable cron, but don't set up any system cron jobs
# Inquisitor updates will be managed manually
services.cron.enable = true;
}

View File

@ -0,0 +1,120 @@
# redstring server module
{ pkgs, ... }:
let
# Import package
redstringSource = builtins.fetchGit {
url = "https://git.alogoulogoi.com/Jaculabilis/redstring.git";
ref = "master";
rev = "91dd353ad1d48118452a949b15e100b3035bf297";
};
redstring = pkgs.callPackage redstringSource {};
# Define the data directory
redstringDir = "/var/lib/redstring/";
redstringData = "${redstringDir}docs/";
# Define the service user
redstringUser = {
name = "redstring";
description = "redstring service user";
group = "redstring";
isSystemUser = true;
};
# Create the public server config file in the nix store
publicConfigAttrs = {
root = redstringData;
edit = false;
};
publicConfig = pkgs.writeTextFile { name = "redstring-config-external.json"; text = (builtins.toJSON publicConfigAttrs); };
# Create the private server config file in the nix store
privateConfig = pkgs.writeTextFile {
name = "redstring-config-internal.json";
text = (builtins.toJSON {
root = redstringData;
edit = true;
});
};
# Create a setup script to ensure the data directory exists
redstringSetup = pkgs.writeShellScriptBin "redstring-setup.sh" ''
# Ensure the service directory
${pkgs.coreutils}/bin/mkdir -p ${redstringData}
# Ensure ownership
chown -R ${redstringUser.name} ${redstringDir}
chmod 700 ${redstringDir}
'';
# Create a run script for the public server
publicRun = pkgs.writeShellScriptBin "redstring-run-external.sh" ''
cd ${redstringDir}
${redstring}/bin/gunicorn \
--bind=localhost:24144 \
--workers=3 \
--log-level debug \
--env REDSTRING_CONFIG=${publicConfig} \
"redstring.server:wsgi()"
'';
# Create a run script for the private server
privateRun = pkgs.writeShellScriptBin "redstring-run-internal.sh" ''
cd ${redstringDir};
${redstring}/bin/gunicorn \
--bind=10.7.3.1:24145 \
--workers=3 \
--log-level debug \
--env REDSTRING_CONFIG=${privateConfig} \
"redstring.server:wsgi()"
'';
in
{
users.users.redstring = redstringUser;
users.groups.redstring = {};
# Run the setup script on activation
system.activationScripts.redstringSetup = "${redstringSetup}/bin/redstring-setup.sh";
# Set up the public redstring service
systemd.services."redstring-public" =
{
description = "redstring public read-only server";
script = "${publicRun}/bin/redstring-run-external.sh";
serviceConfig = {
User = "${redstringUser.name}";
Type = "simple";
};
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
enable = true;
};
# Set up the private redstring service
systemd.services."redstring-private" =
{
description = "redstring private editable server";
script = "${privateRun}/bin/redstring-run-internal.sh";
serviceConfig = {
User = redstringUser.name;
Type = "simple";
};
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
enable = true;
};
# Configure nginx to forward to the public server at the docs subdomain
services.nginx.virtualHosts."docs.alogoulogoi.com" = {
enableACME = true;
forceSSL = true;
extraConfig = ''
access_log /var/log/nginx/access.docs.log;
'';
locations."/".proxyPass = "http://localhost:24144";
};
# Open the firewall to the private server's port
networking.firewall.allowedTCPPorts = [ 24145 ];
}

View File

@ -1,75 +0,0 @@
{ pkgs, ... }:
# exiftool config and script based on that of @numinit
let
# An exiftool config that adds the OriginTimestamp property containing the best guess for file creation time
exiftool-config = pkgs.writeText "exiftool.config" ''
%Image::ExifTool::UserDefined = (
'Image::ExifTool::Composite' => {
OriginTimestamp => {
Desire => {
0 => 'CreateDate',
1 => 'DateTimeOriginal',
2 => 'FileModifyDate'
},
ValueConv => '$val[0] || $val[1] || $val[2] || undef',
PrintConv => '$self->ConvertDateTime($val)',
PrintConvInv => '$self->InverseDateTime($val)'
}
}
)
'';
# A tool to use `find` and `exiftool` to sort images into YMD folders
sibd = pkgs.writeShellScriptBin "sort-images-by-date" ''
set -euo pipefail
if [ $# -lt 3 ]; then
echo "Usage: $0 [prefix] [src] [dest] [exiftool args...]" >&2
exit 1
fi
prefix="$1"
src="$2"
dest="$3"
# Remove $1, $2, $3 from $@ so the remainder can be passed to exiftool
shift; shift; shift
# This find expression matches .jpg/.png modified 14+ days ago.
# The -not (-name -prune) expressions exclude directories from the results by name.
# The exiftool command will move matched files to $dest/$prefix/year/month/day/filename.ext.
# -config loads the config defined above, which adds the OriginTimestamp composite property
# -filename tells exiftool to move each file to a new filename, $OriginTimestamp
# -d defines how the timestamp will be converted to a string, which ends up handling the pathing
${pkgs.findutils}/bin/find $src \
-not \( -name FalseKnees -prune \) \
-not \( -name Background -prune \) \
-type f \
-mtime +14 \
\( -name "*.jpg" -or -name "*.png" \) \
-exec ${pkgs.exiftool}/bin/exiftool \
-config ${exiftool-config} \
-api largefilesupport=1 \
-progress \
-filename'<OriginTimestamp' \
-d "''${dest}/''${prefix}/%Y-%m-%d/%%f%%-c.%%le" \
"$@" \
{} +
'';
# Script for cron jobs that sends output to journalctl -t sync-pipeline
sibd-cron = prefix: src: dest: pkgs.writeShellScript "sibd-cron" ''
${pkgs.systemd}/bin/systemd-cat -t sync-pipeline ${sibd}/bin/sort-images-by-date ${prefix} ${src} ${dest}
'';
in {
environment.systemPackages = [ sibd ];
services.cron = {
enable = true;
systemCronJobs = [
"0 0 * * 1 tvb ${sibd-cron "nokia" "/home/tvb/phone-sync/DCIM" "/home/tvb/phone-sync/staging"}"
"0 0 * * 1 tvb ${sibd-cron "nokia" "/home/tvb/phone-sync/Pictures" "/home/tvb/phone-sync/staging"}"
];
};
}

View File

@ -1,80 +0,0 @@
{ config, pkgs, ... }:
{
imports =
[
./hardware-configuration.nix
];
beatific.hostName = "imperium";
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.networkmanager.enable = true;
time.timeZone = "America/Los_Angeles";
services.xserver = {
enable = true;
displayManager.gdm.enable = true;
desktopManager.gnome.enable = true;
# keyboard
layout = "us";
xkbVariant = "";
};
# Enable sound with pipewire.
sound.enable = true;
hardware.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
# This seems to be sufficient to autodetect the printing functionality of HP OfficeJet 6950
services.printing.enable = true;
services.printing.drivers = [ pkgs.hplip ];
services.avahi = {
enable = true;
nssmdns = true;
openFirewall = true;
};
users.users.tvb = {
extraGroups = [ "networkmanager" ];
};
# Configs needed to run TF2 on integrated graphics
programs.steam.enable = true;
nixpkgs.config.allowUnfree = true;
services.xserver.videoDrivers = [ "i915" ];
hardware.opengl.enable = true;
hardware.opengl.driSupport32Bit = true;
beatific.extraPrograms = true;
environment.systemPackages = with pkgs; [
comic-mono
firefox
gnome.gnome-terminal
libreoffice
mpv
obsidian
unzip
];
networking.firewall = {
enable = false;
};
# This value governs how some stateful data, like databases, are handled
# across different versions of NixOS. This should not be changed to a new
# release unless the sysadmin has determined that no services would be
# adversely affected by changing this.
system.stateVersion = "23.11";
}

View File

@ -1,40 +0,0 @@
# 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 + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/75addc56-2a0d-431e-a0c5-f6ee0e370e61";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/610F-1EB7";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/cc464bb4-e1c8-46c0-adbb-ea1a3cfa5b03"; }
];
# 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.eno1.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@ -16,10 +16,9 @@ in
startMenuLaunchers = false;
};
#system.stateVersion = "21.11";
system.stateVersion = "21.11";
networking.hostName = "stagirite";
beatific.defaults.tvbSync = false;
fileSystems.winssh = {
device = "C:/Users/tvb/.ssh";

View File

@ -1,103 +0,0 @@
{ config, pkgs, lib, ... }:
{
imports =
[
./hardware-configuration.nix
];
beatific.hostName = "unfolder";
beatific.extraPrograms = true;
# Bootloader.
boot.loader.grub = {
enable = true;
device = "/dev/sda";
};
networking.networkmanager.enable = true;
# Override time zone to PST
time.timeZone = "America/Los_Angeles";
# Enable Gnome because it has a virtual keyboard for tablet mode
services.xserver = {
enable = true;
displayManager.gdm.enable = true;
desktopManager.gnome.enable = true;
# keymap configuration
layout = "us";
xkbVariant = "";
};
# Enable sound with pipewire.
sound.enable = true;
hardware.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
systemd.user.services.yoga-rotate = {
enable = false; # TODO
description = "ThinkPad Yoga display rotation";
#wantedBy = ["multi-user.target"];
wantedBy = [ "graphical-session.target" ];
#requires = ["display-manager.service"];
partOf = [ "graphical-session.target" ];
#after = ["display-manager.service"];
path = [ pkgs.gnome-randr ];
serviceConfig = {
#Type = "simple";
ExecStart = "${pkgs.python3}/bin/python /home/tvb/rotate.py";
#KillMode = "process";
#User = "tvb";
Restart = "on-failure";
RestartSec = 5;
};
#environment = {
# DISPLAY = ":0";
# XAUTHORITY = "/home/tvb/.Xauthority";
#};
};
users.users.tvb = {
extraGroups = [ "networkmanager" ];
packages = with pkgs; [
gnome-randr python3
];
};
nixpkgs.config.allowUnfree = true;
environment.systemPackages = with pkgs; [
comic-mono
firefox
gnome.gnome-terminal
libreoffice
obsidian
mpv
];
networking.firewall = {
enable = true;
};
# This seems to be sufficient to autodetect the printing functionality of HP OfficeJet 6950
services.printing.enable = true;
services.printing.drivers = [ pkgs.hplip ];
services.avahi = {
enable = true;
nssmdns = true;
openFirewall = true;
};
# This value governs how some stateful data, like databases, are handled
# across different versions of NixOS. This should not be changed to a new
# release unless the sysadmin has determined that no services would be
# adversely affected by changing this.
system.stateVersion = "22.11";
}

View File

@ -1,34 +0,0 @@
# 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 + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/3a4363d6-c437-45ab-9f35-6831eb4a2cd8";
fsType = "ext4";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/4a083b38-322e-4d35-9877-a8dc1cda21d5"; }
];
# 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.wlp4s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@ -1,46 +0,0 @@
{
beatific = ''
____ ______ _______ _____ ______ _____ ______ ./|,,/|
| _ \| ____| /\ |__ __|_ _| ____|_ _|/ ____/ < o o|
| |_) | |__ / \ | | | | | |__ | | | | <\ ( |
| _ <| __| / /\ \ | | | | | __| | | | | <\\ |\ |
| |_) | |____/ ____ \ | | _| |_| | _| |_| |___<\\\ |(__)
|____/|_____/_/ \_\|_| |_____|_| |_____|\_____|\\ |
'';
backyard = ''
____ ______ _ ____ __ _____ _____ ./|,,/|
| _ \ /\ / ____/| | / /\ \ / / /\ | __ \| __ \ < o o|
| |_) | / \ | | | |/ / \ \ / / / \ | |__) | | | |</ ( |
| _ < / /\ \| | | < \ V / / /\ \ | _ /| | | |// |\ |
| |_) / ____ | |____ | |\ \ | | / ____ \| | \ \| |__| |// |(__)
|____/_/ \_\_____\|_| \_\ |_|/_/ \_|_| \_|_____//// |
'';
catacomb = ''
______ _______ ______ ____ __ __ ____ ./|,,/|
/ ____/ /\ |__ __| /\ / ____// __ \| \ / | _ \ < o o|
| | / \ | | / \ | | | | | | . \/ . | |_) | ( |
| | / /\ \ | | / /\ \| | | | | | |\ /| | _ <\\ |\ |
| |____ / ____ \ | | / ____ | |__ _| |__| | | \/ | | |_) |\ |(__)
\_____/_/ \_\|_|/_/ \_\_____\\____/|_| |_|____/\\ |
'';
empyrean = ''
______ __ __ _______ _______ ______ _ _ ./|,,/|
| ____| \ / | __ \ \ / | __ \| ____| /\ | \ | | < o o|
| |__ | . \/ . | |__) \ \ / /| |__) | |__ / \ | \| |</ ( |
| __| | |\ /| | ___/ \ V / | _ /| __| / /\ \ | . ' |// |\ |
| |____| | \/ | | | | | | | \ \| |____ / ____ \| |\ |// |(__)
|______|_| |_|_| |_| |_| \_|______/_/ \_|_| \_|// |
'';
unfolder = ''
_ _ _ _ ______ ____ _ _____ ______ _____ ./|,,/|
| | | | \ | | ____|/ __ \| | | __ \| ____| __ \ < o o|
| | | | \| | |__ | | | | | | | | | |__ | |__) |\ ( |
| | | | . ' | __| | | | | | | | | | __| | _ /\\ |\ |
| |__| | |\ | | | |__| | |___| |__| | |____| | \ \\\ |(__)
\____/|_| \_|_| \____/|_____|_____/|______|_| <\_\\ |
'';
}

View File

@ -1,32 +0,0 @@
# Set the title bar to user@host: pwd
_TITLE_BAR="\u@\h: \w"
_SET_TITLE_BAR="\[\e]0;$_TITLE_BAR\a\]"
# Shorten $HOME to ~ in PWD
_pwd_home () {
if [[ "$PWD" =~ ^"$HOME"(/|$) ]]; then
echo "~${PWD#$HOME}"
else
echo $PWD
fi
}
# Shorten path dir names
_pwd () {
DIR=$(_pwd_home)
[ "$DIR" != "/" ] && [ "$DIR" != "~" ] && printf '%s/' $(dirname "$DIR" | tr / '\n' | cut -c-1)
printf "$(basename "$DIR")"
}
_DIR='$(_pwd)'
# Color codes
_GREEN="\[\033[32;1m\]"
_DIM="\[\e[32;2m\]"
_OLIVE="\[\e[33;2m\]"
_RESET="\[\e[0m\]"
_HOST=$(if [ -z "$SSH_CLIENT" ]; then echo $_DIM; else echo $_OLIVE; fi)
# Nested shell level
_SHLVL=$(printf '\$%.0s' $(seq 1 $SHLVL))
# SSH detection
_SSH='$([ ! -z "$SSH_CLIENT" ] && echo "=>")'
# Build prompt
export PS1="$_SET_TITLE_BAR$_DIM[\A \u@$_RESET$_HOST\h$_DIM:$_RESET$_GREEN$_DIR$_RESET$_DIM]$_SHLVL$_RESET "
export HISTCONTROL=ignoreboth

View File

@ -1,252 +1,23 @@
{ config, lib, pkgs, ... }:
# Shared configuration values
let
inherit (lib) mkDefault mkIf mkMerge mkOption mkOverride types;
cfg = config.beatific;
mkFlag = description: mkOption {
type = types.bool;
inherit description;
default = true;
};
nebula-port = 4242;
empyrean-vpn-ip = "10.22.20.1";
empyrean-ext-dns = "vpn.alogoulogoi.com";
in {
options = {
beatific = {
# The host name is reused for beatific-specific configuration.
# The bulk of common config is handled in beatific.defaults below, but
# having one option without a default ensures that the module cannot be
# imported accidentally.
hostName = mkOption {
type = types.str;
description = "Hostname";
};
nebula-defaults = {
listen.port = nebula-port;
isLighthouse = mkOption {
type = types.bool;
description = "Whether this host is a Nebula lighthouse";
default = false;
};
# Don't filter at the VPN level
firewall.outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
firewall.inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
extraPrograms = mkOption {
type = types.bool;
description = "Additional default programs";
default = false;
};
# Groups of related defaults can be disabled by flipping off the switches here:
# beatific.defaults.${category} = false;
# They default to true because the point is to do these things by default.
defaults = {
time = mkFlag "Default time zone and NTP";
i18n = mkFlag "Default locale settings";
programs = mkFlag "Default installed programs";
ssh = mkFlag "Default sshd settings";
nebula = mkFlag "Default beatific nebula settings";
tvb = mkFlag "Default tvb account";
tvbSync = mkFlag "Configure system syncthing for tvb";
hosts = mkFlag "Default 10.22.20.* DNS host entries";
};
settings = {
# Enable UDP holepunching both ways, which allows nodes to establish more direct connections with each other
punchy = { punch = true; response = true; };
};
};
config = mkMerge [
{
# Options to always set
networking.hostName = cfg.hostName;
nix.extraOptions = "experimental-features = nix-command flakes";
# Link /etc/nixos to the flake source
environment.etc.nixos.source = ./..;
environment.etc."bashrc.local".source = ./bashrc;
environment.shellAliases = {
# Shortcut for nixos-rebuild
nr = "sudo nixos-rebuild --fast --flake $HOME/nixos-configs";
# Always preserve mode, ownership, ts with copy
cp = "cp -rp";
xo = "xdg-open";
smv = "rsync -v --remove-source-files";
".." = "cd ..";
"..." = "cd ../..";
"...." = "cd ../../..";
"....." = "cd ../../../..";
};
security.sudo.extraRules = [{
users = [ "tvb" ];
commands = [ { command = "/run/current-system/sw/bin/nixos-rebuild"; options = [ "NOPASSWD" ]; } ];
}];
}
(mkIf cfg.defaults.time {
# mkDefault time zone to make it easy to configure it to non-UTC
time.timeZone = mkDefault "UTC";
services.ntp.enable = true;
services.ntp.servers = [ "time.nist.gov" ];
})
(mkIf cfg.defaults.i18n {
# en_US.UTF-8
i18n.defaultLocale = "en_US.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "en_US.UTF-8";
LC_IDENTIFICATION = "en_US.UTF-8";
LC_MEASUREMENT = "en_US.UTF-8";
LC_MONETARY = "en_US.UTF-8";
LC_NAME = "en_US.UTF-8";
LC_NUMERIC = "en_US.UTF-8";
LC_PAPER = "en_US.UTF-8";
LC_TELEPHONE = "en_US.UTF-8";
LC_TIME = "en_US.UTF-8";
};
})
(mkIf cfg.defaults.programs {
environment.systemPackages = with pkgs; [
bc # Terminal calculator
curl
duf
exiftool
file # File type inspector
htmlq # jq for html
jq # jq for json
nebula
psmisc # provides killall
python3
rsync
sqlite
tree # Directory tree view
unzip
vim
wget
zip
(writeShellScriptBin "clip" ''
${xclip}/bin/xclip -sel c < $1
'')
];
programs = {
git = {
enable = true;
config.init.defaultBranch = "master";
};
htop.enable = true;
};
# The nixpkgs default is "nano", so we go one priority higher
environment.variables.EDITOR = mkOverride 999 "vim";
})
(mkIf cfg.extraPrograms {
environment.systemPackages = with pkgs; [
calibre # provides ebook-convert
imagemagick # image convertion cli
puddletag # mp3 tag editor
tesseract # OCR engine
];
})
(mkIf cfg.defaults.ssh {
services.openssh.enable = true;
services.openssh.banner = let
ascii = import ./ascii.nix;
in ascii.${cfg.hostName} or ascii.beatific;
networking.firewall.allowPing = true;
networking.firewall.allowedTCPPorts = [ 22 ];
})
(mkIf cfg.defaults.nebula {
services.nebula.networks.beatific = let
empyreanExternalDns = "vpn.alogoulogoi.com";
empyreanInternalIp = "10.22.20.1";
nebulaPort = 4242;
in {
enable = true;
# The lighthouse only listens on the designated subdomain
listen.host = if cfg.isLighthouse then empyreanExternalDns else "0.0.0.0";
listen.port = nebulaPort;
# Standard certificate paths
ca = "/etc/nebula/beatific/beatific.crt";
cert = "/etc/nebula/beatific/${cfg.hostName}.crt";
key = "/etc/nebula/beatific/${cfg.hostName}.key";
isLighthouse = cfg.isLighthouse;
# Non-lighthouses connect to the lighthouse at empyrean
# This should be a VPN address in the static host map
lighthouses = mkIf (! cfg.isLighthouse) [ empyreanInternalIp ];
# Currently there is no VPN-level traffic filtering
firewall.outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
firewall.inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
# Map the lighthouse address to its public address
staticHostMap = { ${empyreanInternalIp} = [ "${empyreanExternalDns}:${toString nebulaPort}" ]; };
settings = {
# Enable UDP holepunching both ways, which allows nodes to establish more direct connections with each other
punchy = { punch = true; response = true; };
};
};
})
(mkIf cfg.defaults.tvb {
users.groups.tvb = {};
users.users.tvb = {
isNormalUser = true;
group = "tvb";
extraGroups = [ "wheel" ];
initialPassword = "password";
openssh.authorizedKeys.keyFiles = (import ../keys).tvb;
};
})
(mkIf cfg.defaults.tvbSync {
# I haven't gotten user services to work correctly yet,
# so for now, tvb monopolizes the system syncthing instance.
# Adding users in the future should probably involve multiple
# system services so as not to require login to sync.
services.syncthing = {
enable = true;
configDir = "/home/tvb/.config/syncthing";
# this doesn't prevent syncthing from putting sync points in other locations, it's just a default
# normally it would make sense to put it at ~ but see https://github.com/NixOS/nixpkgs/pull/273693
dataDir = "/home/tvb/.config/syncthing";
openDefaultPorts = true;
user = "tvb";
group = "tvb";
};
})
(mkIf cfg.defaults.hosts {
# Create *.home host entries for all the beatific members
networking.hosts = {
"10.22.20.1" = [
"empyrean.home"
];
"10.22.20.2" = [
"catacomb.home"
"mirror.catacomb.home"
];
"10.22.20.3" = [
"palamas.home"
];
"10.22.20.4" = [
"stagirite.home"
];
"10.22.20.5" = [
"vagrant.home"
];
"10.22.20.6" = [
"unfolder.home"
];
"10.22.20.7" = [
"centroid.home"
"mopidy.home.ktvb.site"
];
"10.22.20.8" = [
"backyard.home"
"jellyfin.home.ktvb.site"
];
"10.22.20.9" = [
"imperium.home"
];
};
})
];
inherit empyrean-vpn-ip;
empyrean-host-map = { ${empyrean-vpn-ip} = [ "${empyrean-ext-dns}:${toString nebula-port}" ]; };
}

View File

@ -1,722 +0,0 @@
{ config, lib, options, pkgs, ... }:
with lib;
let
cfg = config.services.syncthing;
opt = options.services.syncthing;
defaultUser = "syncthing";
defaultGroup = defaultUser;
settingsFormat = pkgs.formats.json { };
cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings;
isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/";
# Syncthing supports serving the GUI over Unix sockets. If that happens, the
# API is served over the Unix socket as well. This function returns the correct
# curl arguments for the address portion of the curl command for both network
# and Unix socket addresses.
curlAddressArgs = path: if isUnixGui
# if cfg.guiAddress is a unix socket, tell curl explicitly about it
# note that the dot in front of `${path}` is the hostname, which is
# required.
then "--unix-socket ${cfg.guiAddress} http://.${path}"
# no adjustements are needed if cfg.guiAddress is a network address
else "${cfg.guiAddress}${path}"
;
devices = mapAttrsToList (_: device: device // {
deviceID = device.id;
}) cfg.settings.devices;
folders = mapAttrsToList (_: folder: folder //
throwIf (folder?rescanInterval || folder?watch || folder?watchDelay) ''
The options services.syncthing.settings.folders.<name>.{rescanInterval,watch,watchDelay}
were removed. Please use, respectively, {rescanIntervalS,fsWatcherEnabled,fsWatcherDelayS} instead.
'' {
devices = map (device:
if builtins.isString device then
{ deviceId = cfg.settings.devices.${device}.id; }
else
device
) folder.devices;
}) (filterAttrs (_: folder:
folder.enable
) cfg.settings.folders);
jq = "${pkgs.jq}/bin/jq";
updateConfig = pkgs.writers.writeBash "merge-syncthing-config" (''
set -efu
# be careful not to leak secrets in the filesystem or in process listings
umask 0077
curl() {
# get the api key by parsing the config.xml
while
! ${pkgs.libxml2}/bin/xmllint \
--xpath 'string(configuration/gui/apikey)' \
${cfg.configDir}/config.xml \
>"$RUNTIME_DIRECTORY/api_key"
do sleep 1; done
(printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
--retry 1000 --retry-delay 1 --retry-all-errors \
"$@"
}
'' +
/* Syncthing's rest API for the folders and devices is almost identical.
Hence we iterate them using lib.pipe and generate shell commands for both at
the sime time. */
(lib.pipe {
# The attributes below are the only ones that are different for devices /
# folders.
devs = {
new_conf_IDs = map (v: v.id) devices;
GET_IdAttrName = "deviceID";
override = cfg.overrideDevices;
conf = devices;
baseAddress = curlAddressArgs "/rest/config/devices";
};
dirs = {
new_conf_IDs = map (v: v.id) folders;
GET_IdAttrName = "id";
override = cfg.overrideFolders;
conf = folders;
baseAddress = curlAddressArgs "/rest/config/folders";
};
} [
# Now for each of these attributes, write the curl commands that are
# identical to both folders and devices.
(mapAttrs (conf_type: s:
# We iterate the `conf` list now, and run a curl -X POST command for each, that
# should update that device/folder only.
lib.pipe s.conf [
# Quoting https://docs.syncthing.net/rest/config.html:
#
# > PUT takes an array and POST a single object. In both cases if a
# given folder/device already exists, its replaced, otherwise a new
# one is added.
#
# What's not documented, is that using PUT will remove objects that
# don't exist in the array given. That's why we use here `POST`, and
# only if s.override == true then we DELETE the relevant folders
# afterwards.
(map (new_cfg: ''
curl -d ${lib.escapeShellArg (builtins.toJSON new_cfg)} -X POST ${s.baseAddress}
''))
(lib.concatStringsSep "\n")
]
/* If we need to override devices/folders, we iterate all currently configured
IDs, via another `curl -X GET`, and we delete all IDs that are not part of
the Nix configured list of IDs
*/
+ lib.optionalString s.override ''
stale_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} \
--argjson new_ids ${lib.escapeShellArg (builtins.toJSON s.new_conf_IDs)} \
--raw-output \
'[.[].${s.GET_IdAttrName}] - $new_ids | .[]'
)"
for id in ''${stale_${conf_type}_ids}; do
curl -X DELETE ${s.baseAddress}/$id
done
''
))
builtins.attrValues
(lib.concatStringsSep "\n")
]) +
/* Now we update the other settings defined in cleanedConfig which are not
"folders" or "devices". */
(lib.pipe cleanedConfig [
builtins.attrNames
(lib.subtractLists ["folders" "devices"])
(map (subOption: ''
curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"}
''))
(lib.concatStringsSep "\n")
]) + ''
# restart Syncthing if required
if curl ${curlAddressArgs "/rest/config/restart-required"} |
${jq} -e .requiresRestart > /dev/null; then
curl -X POST ${curlAddressArgs "/rest/system/restart"}
fi
'');
in {
###### interface
options = {
services.syncthing = {
enable = mkEnableOption
(lib.mdDoc "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync");
cert = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc ''
Path to the `cert.pem` file, which will be copied into Syncthing's
[configDir](#opt-services.syncthing.configDir).
'';
};
key = mkOption {
type = types.nullOr types.str;
default = null;
description = mdDoc ''
Path to the `key.pem` file, which will be copied into Syncthing's
[configDir](#opt-services.syncthing.configDir).
'';
};
overrideDevices = mkOption {
type = types.bool;
default = true;
description = mdDoc ''
Whether to delete the devices which are not configured via the
[devices](#opt-services.syncthing.settings.devices) option.
If set to `false`, devices added via the web
interface will persist and will have to be deleted manually.
'';
};
overrideFolders = mkOption {
type = types.bool;
default = true;
description = mdDoc ''
Whether to delete the folders which are not configured via the
[folders](#opt-services.syncthing.settings.folders) option.
If set to `false`, folders added via the web
interface will persist and will have to be deleted manually.
'';
};
settings = mkOption {
type = types.submodule {
freeformType = settingsFormat.type;
options = {
# global options
options = mkOption {
default = {};
description = mdDoc ''
The options element contains all other global configuration options
'';
type = types.submodule ({ name, ... }: {
freeformType = settingsFormat.type;
options = {
localAnnounceEnabled = mkOption {
type = types.nullOr types.bool;
default = null;
description = lib.mdDoc ''
Whether to send announcements to the local LAN, also use such announcements to find other devices.
'';
};
localAnnouncePort = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
The port on which to listen and send IPv4 broadcast announcements to.
'';
};
relaysEnabled = mkOption {
type = types.nullOr types.bool;
default = null;
description = lib.mdDoc ''
When true, relays will be connected to and potentially used for device to device connections.
'';
};
urAccepted = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
Whether the user has accepted to submit anonymous usage data.
The default, 0, mean the user has not made a choice, and Syncthing will ask at some point in the future.
"-1" means no, a number above zero means that that version of usage reporting has been accepted.
'';
};
limitBandwidthInLan = mkOption {
type = types.nullOr types.bool;
default = null;
description = lib.mdDoc ''
Whether to apply bandwidth limits to devices in the same broadcast domain as the local device.
'';
};
maxFolderConcurrency = mkOption {
type = types.nullOr types.int;
default = null;
description = lib.mdDoc ''
This option controls how many folders may concurrently be in I/O-intensive operations such as syncing or scanning.
The mechanism is described in detail in a [separate chapter](https://docs.syncthing.net/advanced/option-max-concurrency.html).
'';
};
};
});
};
# device settings
devices = mkOption {
default = {};
description = mdDoc ''
Peers/devices which Syncthing should communicate with.
Note that you can still add devices manually, but those changes
will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices)
is enabled.
'';
example = {
bigbox = {
id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
addresses = [ "tcp://192.168.0.10:51820" ];
};
};
type = types.attrsOf (types.submodule ({ name, ... }: {
freeformType = settingsFormat.type;
options = {
name = mkOption {
type = types.str;
default = name;
description = lib.mdDoc ''
The name of the device.
'';
};
id = mkOption {
type = types.str;
description = mdDoc ''
The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
'';
};
autoAcceptFolders = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Automatically create or share folders that this device advertises at the default path.
See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
'';
};
};
}));
};
# folder settings
folders = mkOption {
default = {};
description = mdDoc ''
Folders which should be shared by Syncthing.
Note that you can still add folders manually, but those changes
will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders)
is enabled.
'';
example = literalExpression ''
{
"/home/user/sync" = {
id = "syncme";
devices = [ "bigbox" ];
};
}
'';
type = types.attrsOf (types.submodule ({ name, ... }: {
freeformType = settingsFormat.type;
options = {
enable = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
Whether to share this folder.
This option is useful when you want to define all folders
in one place, but not every machine should share all folders.
'';
};
path = mkOption {
# TODO for release 23.05: allow relative paths again and set
# working directory to cfg.dataDir
type = types.str // {
check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/");
description = types.str.description + " starting with / or ~/";
};
default = name;
description = lib.mdDoc ''
The path to the folder which should be shared.
Only absolute paths (starting with `/`) and paths relative to
the [user](#opt-services.syncthing.user)'s home directory
(starting with `~/`) are allowed.
'';
};
id = mkOption {
type = types.str;
default = name;
description = lib.mdDoc ''
The ID of the folder. Must be the same on all devices.
'';
};
label = mkOption {
type = types.str;
default = name;
description = lib.mdDoc ''
The label of the folder.
'';
};
devices = mkOption {
type = types.listOf types.str;
default = [];
description = mdDoc ''
The devices this folder should be shared with. Each device must
be defined in the [devices](#opt-services.syncthing.settings.devices) option.
'';
};
versioning = mkOption {
default = null;
description = mdDoc ''
How to keep changed/deleted files with Syncthing.
There are 4 different types of versioning with different parameters.
See <https://docs.syncthing.net/users/versioning.html>.
'';
example = literalExpression ''
[
{
versioning = {
type = "simple";
params.keep = "10";
};
}
{
versioning = {
type = "trashcan";
params.cleanoutDays = "1000";
};
}
{
versioning = {
type = "staggered";
fsPath = "/syncthing/backup";
params = {
cleanInterval = "3600";
maxAge = "31536000";
};
};
}
{
versioning = {
type = "external";
params.versionsPath = pkgs.writers.writeBash "backup" '''
folderpath="$1"
filepath="$2"
rm -rf "$folderpath/$filepath"
''';
};
}
]
'';
type = with types; nullOr (submodule {
freeformType = settingsFormat.type;
options = {
type = mkOption {
type = enum [ "external" "simple" "staggered" "trashcan" ];
description = mdDoc ''
The type of versioning.
See <https://docs.syncthing.net/users/versioning.html>.
'';
};
};
});
};
copyOwnershipFromParent = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
On Unix systems, tries to copy file/folder ownership from the parent directory (the directory its located in).
Requires running Syncthing as a privileged user, or granting it additional capabilities (e.g. CAP_CHOWN on Linux).
'';
};
};
}));
};
};
};
default = {};
description = mdDoc ''
Extra configuration options for Syncthing.
See <https://docs.syncthing.net/users/config.html>.
Note that this attribute set does not exactly match the documented
xml format. Instead, this is the format of the json rest api. There
are slight differences. For example, this xml:
```xml
<options>
<listenAddress>default</listenAddress>
<minHomeDiskFree unit="%">1</minHomeDiskFree>
</options>
```
corresponds to the json:
```json
{
options: {
listenAddresses = [
"default"
];
minHomeDiskFree = {
unit = "%";
value = 1;
};
};
}
```
'';
example = {
options.localAnnounceEnabled = false;
gui.theme = "black";
};
};
guiAddress = mkOption {
type = types.str;
default = "127.0.0.1:8384";
description = lib.mdDoc ''
The address to serve the web interface at.
'';
};
systemService = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
Whether to auto-launch Syncthing as a system service.
'';
};
user = mkOption {
type = types.str;
default = defaultUser;
example = "yourUser";
description = mdDoc ''
The user to run Syncthing as.
By default, a user named `${defaultUser}` will be created whose home
directory is [dataDir](#opt-services.syncthing.dataDir).
'';
};
group = mkOption {
type = types.str;
default = defaultGroup;
example = "yourGroup";
description = mdDoc ''
The group to run Syncthing under.
By default, a group named `${defaultGroup}` will be created.
'';
};
all_proxy = mkOption {
type = with types; nullOr str;
default = null;
example = "socks5://address.com:1234";
description = mdDoc ''
Overwrites the all_proxy environment variable for the Syncthing process to
the given value. This is normally used to let Syncthing connect
through a SOCKS5 proxy server.
See <https://docs.syncthing.net/users/proxying.html>.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/syncthing";
example = "/home/yourUser";
description = lib.mdDoc ''
The path where synchronised directories will exist.
'';
};
configDir = let
cond = versionAtLeast config.system.stateVersion "19.03";
in mkOption {
type = types.path;
description = lib.mdDoc ''
The path where the settings and keys will exist.
'';
default = cfg.dataDir + optionalString cond "/.config/syncthing";
defaultText = literalMD ''
* if `stateVersion >= 19.03`:
config.${opt.dataDir} + "/.config/syncthing"
* otherwise:
config.${opt.dataDir}
'';
};
databaseDir = mkOption {
type = types.path;
description = lib.mdDoc ''
The directory containing the database and logs.
'';
default = cfg.configDir;
defaultText = literalExpression "config.${opt.configDir}";
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [];
example = [ "--reset-deltas" ];
description = lib.mdDoc ''
Extra flags passed to the syncthing command in the service definition.
'';
};
openDefaultPorts = mkOption {
type = types.bool;
default = false;
example = true;
description = lib.mdDoc ''
Whether to open the default ports in the firewall: TCP/UDP 22000 for transfers
and UDP 21027 for discovery.
If multiple users are running Syncthing on this machine, you will need
to manually open a set of ports for each instance and leave this disabled.
Alternatively, if you are running only a single instance on this machine
using the default ports, enable this.
'';
};
package = mkOption {
type = types.package;
default = pkgs.syncthing;
defaultText = literalExpression "pkgs.syncthing";
description = lib.mdDoc ''
The Syncthing package to use.
'';
};
};
};
imports = [
(mkRemovedOptionModule [ "services" "syncthing" "useInotify" ] ''
This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher".
It can be enabled on a per-folder basis through the web interface.
'')
(mkRenamedOptionModule [ "services" "syncthing" "extraOptions" ] [ "services" "syncthing" "settings" ])
(mkRenamedOptionModule [ "services" "syncthing" "folders" ] [ "services" "syncthing" "settings" "folders" ])
(mkRenamedOptionModule [ "services" "syncthing" "devices" ] [ "services" "syncthing" "settings" "devices" ])
(mkRenamedOptionModule [ "services" "syncthing" "options" ] [ "services" "syncthing" "settings" "options" ])
] ++ map (o:
mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ]
) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"];
###### implementation
config = mkIf cfg.enable {
networking.firewall = mkIf cfg.openDefaultPorts {
allowedTCPPorts = [ 22000 ];
allowedUDPPorts = [ 21027 22000 ];
};
systemd.packages = [ pkgs.syncthing ];
users.users = mkIf (cfg.systemService && cfg.user == defaultUser) {
${defaultUser} =
{ group = cfg.group;
home = cfg.dataDir;
createHome = true;
uid = config.ids.uids.syncthing;
description = "Syncthing daemon user";
};
};
users.groups = mkIf (cfg.systemService && cfg.group == defaultGroup) {
${defaultGroup}.gid =
config.ids.gids.syncthing;
};
systemd.services = {
# upstream reference:
# https://github.com/syncthing/syncthing/blob/main/etc/linux-systemd/system/syncthing%40.service
syncthing = mkIf cfg.systemService {
description = "Syncthing service";
after = [ "network.target" ];
environment = {
STNORESTART = "yes";
STNOUPGRADE = "yes";
inherit (cfg) all_proxy;
} // config.networking.proxy.envVars;
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Restart = "on-failure";
SuccessExitStatus = "3 4";
RestartForceExitStatus="3 4";
User = cfg.user;
Group = cfg.group;
ExecStartPre = mkIf (cfg.cert != null || cfg.key != null)
"+${pkgs.writers.writeBash "syncthing-copy-keys" ''
install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir}
${optionalString (cfg.cert != null) ''
install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.cert} ${cfg.configDir}/cert.pem
''}
${optionalString (cfg.key != null) ''
install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.key} ${cfg.configDir}/key.pem
''}
''}"
;
ExecStart = ''
${cfg.package}/bin/syncthing \
-no-browser \
-gui-address=${if isUnixGui then "unix://" else ""}${cfg.guiAddress} \
-config=${cfg.configDir} \
-data=${cfg.databaseDir} \
${escapeShellArgs cfg.extraFlags}
'';
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
CapabilityBoundingSet = [
"~CAP_SYS_PTRACE" "~CAP_SYS_ADMIN"
"~CAP_SETGID" "~CAP_SETUID" "~CAP_SETPCAP"
"~CAP_SYS_TIME" "~CAP_KILL"
];
};
};
syncthing-init = mkIf (cleanedConfig != {}) {
description = "Syncthing configuration updater";
requisite = [ "syncthing.service" ];
after = [ "syncthing.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = cfg.user;
RemainAfterExit = true;
RuntimeDirectory = "syncthing-init";
Type = "oneshot";
ExecStart = updateConfig;
};
};
syncthing-resume = {
wantedBy = [ "suspend.target" ];
};
};
};
}