Compare commits

..

No commits in common. "master" and "v0.1" have entirely different histories.
master ... v0.1

29 changed files with 651 additions and 509 deletions

Binary file not shown.

View file

@ -1,68 +0,0 @@
# Server nixos configuration
This is the configuration running on my homebrew server.
It is perfect in and no other config is in any way shape or form better.
# Features
- Forgejo git instance
- Vaultwarden password manager
- Jellyfin media server
- Sonnar, Radarr, Jackett and transmission auto-torrenting setup in declarative [astro/microvm](https://github.com/astro/microvm.nix) virtual machine behind a VPN.
- ZFS RaidZ (Raid5) for data resilience with automated emails for failed drives
- Self-hosted mail server for sending emails
- Simple file structure for easy extension
# How to Install
## Prepare device
Install the [nixos boot ISO](https://nixos.org/download/) and run it on your device.
Once booted run
```sh
$ sudo passwd
$ ip a
```
to set a password for the `nix` user and display your ip.
Once done you can ssh into the device and generate a nix hardware config with
```sh
$ ssh nix@<ip>
$ sudo mkdir /mnt
$ sudo nixos-generate-config --root /mnt --no-filesystem
```
This will generate the file `/mnt/hardware-config.nix` that you can then copy to your
computer.
## Install configuration
### Prepare repo
1. Clone the repo
- `git clone https://git.spoodythe.one/spoody/server-configuration.git`
1. Copy the `hardware-configuration.nix` from your device to the repo folder.
- `scp nix@<ip>:/mnt/hardware-configuration.nix /path/to/repo/hardware-configuration.nix`
1. Add/change the device configuration in `flake.nix`
- This includes configuring which (if any) disks should be included in the raid array, as well as which disk is the main root disk.
### Install remotely
Run the command
```sh
nix run github:nix-community/nixos-anywhere -- --flake '.#<your configuration>' --target-host nixos@<ip>
```
This will automatically install the configuration to your server.
## Copy configuration to server
Once the configuration has been installed push your modified repo to a git service and pull it on the server.

View file

@ -8,23 +8,26 @@
./services/nginx.nix ./services/nginx.nix
./services/openssh.nix ./services/openssh.nix
./services/forgejo.nix ./services/forgejo.nix
# ./services/nextcloud.nix
# ./services/seafile.nix
./services/vaultwarden.nix ./services/vaultwarden.nix
./services/jellyfin.nix ./services/jellyfin.nix
./services/website.nix ./services/website.nix
./services/mailserver.nix ./services/mailserver.nix
./services/fail2ban.nix ./services/fail2ban.nix
./services/zed.nix # IMPORTANTE
./services/auto-torrent.nix
./services/misc.nix
./services/website.nix ./services/zed.nix # IMPORTANTE
./services/auto-torrent.nix
./services/misc.nix
./modules/age.nix ./modules/age.nix
./modules/git.nix ./modules/git.nix
./modules/nix-settings.nix ./modules/nix-settings.nix
./modules/zfs.nix ./modules/zfs.nix
./modules/microvm.nix
(import ./modules/microvm.nix {routed = true;})
(import ./modules/networking-shared.nix {hostname = "server";}) (import ./modules/networking-shared.nix {hostname = "server";})
(import ./modules/users.nix {main-user = "baritone";}) (import ./modules/users.nix {main-user = "baritone";})
]; ];
@ -39,11 +42,21 @@
# Enable microcode updates # Enable microcode updates
hardware.enableRedistributableFirmware = true; hardware.enableRedistributableFirmware = true;
# programs.zsh = {
# enable = true;
# enableGlobalCompInit = true;
# shellAliases = {
# "nrb" = "sudo nixos-rebuild switch --flake /etc/nixos";
# };
# };
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
wget wget
curl curl
git git
vim vim
inputs.agenix.packages."${system}".default
]; ];
programs.nano.enable = false; programs.nano.enable = false;

View file

@ -8,14 +8,12 @@
disko.devices = { disko.devices = {
disk = disk =
{ {
# Main SSD where /nix/store /etc /boot etc. is located.
main = { main = {
type = "disk"; type = "disk";
device = root-disk; device = root-disk;
content = { content = {
type = "gpt"; type = "gpt";
partitions = { partitions = {
# Define a 512 megabyte boot partition
boot = { boot = {
size = "512M"; size = "512M";
type = "EF00"; type = "EF00";
@ -26,7 +24,6 @@
mountOptions = ["umask=0077"]; mountOptions = ["umask=0077"];
}; };
}; };
# If the swap argument is defined then create a swap partition of the given size
swap = lib.mkIf (swap-size != -1) { swap = lib.mkIf (swap-size != -1) {
size = swap-size; size = swap-size;
content = { content = {
@ -35,7 +32,6 @@
resumeDevice = true; resumeDevice = true;
}; };
}; };
# Finally allocate the rest of the disk as a zfs device
root = { root = {
size = "100%"; size = "100%";
content = { content = {
@ -66,13 +62,12 @@
}); });
zpool = { zpool = {
# Set up zroot pool
zroot = { zroot = {
type = "zpool"; type = "zpool";
rootFsOptions = { rootFsOptions = {
mountpoint = "none"; mountpoint = "none";
acltype = "posixacl"; # POSIX compliant file permissions acltype = "posixacl";
xattr = "sa"; # Store attributes in inodes to reduce disk reads xattr = "sa";
}; };
datasets = { datasets = {
@ -88,17 +83,16 @@
}; };
}; };
# If any disks where defined to be in the raid we set up the raid5 pool
raid5 = lib.mkIf (builtins.length raid-disks > 0) { raid5 = lib.mkIf (builtins.length raid-disks > 0) {
type = "zpool"; type = "zpool";
mode = "raidz"; mode = "raidz";
rootFsOptions = { rootFsOptions = {
compression = "zstd"; # Compress filesystem for more storage compression = "zstd";
mountpoint = "none"; # Dont mount the zfs pool anywhere mountpoint = "none";
acltype = "posixacl"; # POSIX compliant file permissions acltype = "posixacl";
xattr = "sa"; # Store attributes in inodes to reduce disk reads xattr = "sa";
"com.sun:auto-snapshot" = "true"; # Never a bad idea to have backups "com.sun:auto-snapshot" = "true";
}; };
datasets = { datasets = {
@ -118,10 +112,10 @@
type = "zfs_fs"; type = "zfs_fs";
mountpoint = "/opt"; mountpoint = "/opt";
}; };
# media = { media = {
# type = "zfs_fs"; type = "zfs_fs";
# mountpoint = "/media"; mountpoint = "/media";
# }; };
}; };
}; };
}; };

View file

@ -143,11 +143,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1743295846, "lastModified": 1742326330,
"narHash": "sha256-hKKz07d4RV9gzxzE5Qu3RQWX8a7XpzRrP5timoxoGRQ=", "narHash": "sha256-Tumt3tcMXJniSh7tw2gW+WAnVLeB3WWm+E+yYFnLBXo=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "717030011980e9eb31eb8ce011261dd532bce92c", "rev": "22a36aa709de7dd42b562a433b9cefecf104a6ee",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -165,11 +165,11 @@
"spectrum": "spectrum" "spectrum": "spectrum"
}, },
"locked": { "locked": {
"lastModified": 1743083165, "lastModified": 1741893540,
"narHash": "sha256-Fz7AiCJWtoWZ2guJwO3B1h3RuJxYWaCzFIqY0Kmkyrs=", "narHash": "sha256-NEsR+FQ+AsY4cOZ3OL69JVdPTAYzSzBqeAhHGRRuDGk=",
"owner": "astro", "owner": "astro",
"repo": "microvm.nix", "repo": "microvm.nix",
"rev": "773d5a04e2e10ca7b412270dea11276a496e1b61", "rev": "5bbc126db87b5ffc36394df630eead403c48fac8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -180,11 +180,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1743095683, "lastModified": 1742288794,
"narHash": "sha256-gWd4urRoLRe8GLVC/3rYRae1h+xfQzt09xOfb0PaHSk=", "narHash": "sha256-Txwa5uO+qpQXrNG4eumPSD+hHzzYi/CdaM80M9XRLCo=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5e5402ecbcb27af32284d4a62553c019a3a49ea6", "rev": "b6eaf97c6960d97350c584de1b6dcff03c9daf42",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,15 +1,4 @@
{ {config, ...}: {
config, # This is where the age files go
pkgs, # age.secrets.nextcloud.file = ../secrets/nextcloud.age;
inputs,
...
}: {
age.identityPaths = [
"/home/baritone/.ssh/id_ed25519"
];
age.secrets = {
forgejo-runner-token.file = ../secrets/forgejo-runner-token.age;
};
environment.systemPackages = [inputs.agenix.packages."${pkgs.system}".default];
} }

View file

@ -1,4 +1,4 @@
{ {routed ? false}: {
pkgs, pkgs,
config, config,
... ...

View file

@ -34,8 +34,5 @@
"nrb" = "sudo nixos-rebuild switch --flake /etc/nixos"; "nrb" = "sudo nixos-rebuild switch --flake /etc/nixos";
"vmr" = "rm ~/.ssh/known_hosts; ssh root@10.0.0.1"; "vmr" = "rm ~/.ssh/known_hosts; ssh root@10.0.0.1";
}; };
shellAliases = {
"fucking" = "sudo";
};
}; };
} }

View file

@ -8,4 +8,8 @@
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
zfs zfs
]; ];
# Microvm be fucking shit up
# fileSystems."/nix/store".fsType = lib.mkForce "zfs";
# fileSystems."/nix/store".device = lib.mkForce "zroot/nix/store";
} }

View file

@ -1,5 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 rgw77A B/0cZTeHin54r5rLOPknrID5HqDdXUP1f3n6q28B11s
I8lGmJNvkJQQUlWD8fbCt+IuYisKhoVb59ulqQZsGJw
--- 5qC6VjzF8ldbN+AuD+G5y8ABxl+hqD1mt8QyEgVxrjI
ÊcåºÃMï·Ö UÞ;œm0à6kª°;‚ª-sŸKÿÀÔWM;·ã_Š<5F>&<26>Yk\èŒMNê1¨qO¨¤hM7k A1m•1

View file

@ -2,5 +2,4 @@ let
public-keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC562Woe/yT/3dNVceN9rKPJQcvgTFzIhJVdVGv7sqn1 baritone@server"]; public-keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC562Woe/yT/3dNVceN9rKPJQcvgTFzIhJVdVGv7sqn1 baritone@server"];
in { in {
"nextcloud.age".publicKeys = public-keys; "nextcloud.age".publicKeys = public-keys;
"forgejo-runner-token.age".publicKeys = public-keys;
} }

View file

@ -5,28 +5,14 @@
}: let }: let
lib = pkgs.lib; lib = pkgs.lib;
host = "10.0.${toString vm-index}.1"; host = "10.0.${toString vm-index}.1";
port = 8989;
ports = {
sonarr.port = 8989;
radarr.port = 7878;
jackett.port = 9117;
};
vm-index = 1; vm-index = 1;
vm-mac = "02:00:00:00:00:02"; vm-mac = "02:00:00:00:00:02";
vm-name = "auto-torrent"; vm-name = "auto-torrent";
vpn-endpoint = "193.32.248.70"; vpn-endpoint = "193.32.248.70";
enable-services = true; enable-services = true;
wg-config = "/mnt/wg.conf";
jellyfin-gid = 989;
ports-list = (pkgs.lib.attrsets.mapAttrsToList (name: value: value.port) ports) ++ [9091];
in { in {
microvm.autostart = [vm-name]; microvm.autostart = [vm-name];
imports = [
./nginx.nix
../modules/microvm.nix
];
users.extraUsers.microvm.extraGroups = [ users.extraUsers.microvm.extraGroups = [
"jellyfin" # access to media folder "jellyfin" # access to media folder
@ -34,7 +20,9 @@ in {
system.activationScripts."make-${vm-name}-data-dir" = lib.stringAfter ["var"] '' system.activationScripts."make-${vm-name}-data-dir" = lib.stringAfter ["var"] ''
mkdir -p /var/lib/${vm-name} mkdir -p /var/lib/${vm-name}
# chown -R microvm:jellyfin /var/lib/${vm-name} chmod -R 777 /var/lib/${vm-name}
chown -R microvm /var/lib/${vm-name}
chmod -R 777 /media
''; '';
microvm.vms.${vm-name} = { microvm.vms.${vm-name} = {
@ -48,8 +36,6 @@ in {
} }
]; ];
time.timeZone = "Europe/Copenhagen";
# 1gb of memory # 1gb of memory
microvm.mem = 1024; microvm.mem = 1024;
@ -69,7 +55,7 @@ in {
} }
{ {
tag = "media-dir"; tag = "media-dir";
source = "/var/lib/media"; source = "/media";
mountPoint = "/media"; mountPoint = "/media";
inherit proto; inherit proto;
} }
@ -115,7 +101,7 @@ in {
serviceConfig = { serviceConfig = {
type = "oneshot"; type = "oneshot";
ExecStart = pkgs.writeShellScript "wgconf.sh" '' ExecStart = pkgs.writeShellScript "wgconf.sh" ''
${pkgs.wireguard-tools}/bin/wg-quick up ${wg-config} ${pkgs.wireguard-tools}/bin/wg-quick up /mnt/wg.conf
''; '';
RemainAfterExit = "yes"; RemainAfterExit = "yes";
}; };
@ -133,18 +119,6 @@ in {
programs.nano.enable = lib.mkForce false; programs.nano.enable = lib.mkForce false;
programs.vim.enable = true; programs.vim.enable = true;
# Fix permissions with groups
users.users = {
sonarr.extraGroups = ["jellyfin"];
radarr.extraGroups = ["jellyfin"];
transmission.extraGroups = ["jellyfin"];
jackett.extraGroups = ["jellyfin"];
};
users.groups.jellyfin = {
gid = jellyfin-gid;
};
# Services # Services
services.sonarr = pkgs.lib.mkIf enable-services { services.sonarr = pkgs.lib.mkIf enable-services {
enable = true; enable = true;
@ -164,25 +138,11 @@ in {
services.transmission = pkgs.lib.mkIf enable-services { services.transmission = pkgs.lib.mkIf enable-services {
enable = true; enable = true;
openPeerPorts = true; openFirewall = true;
openRPCPort = true;
home = "/mnt/transmission"; home = "/mnt/transmission";
webHome = "${pkgs.flood-for-transmission}"; settings.download-dir = "/mnt/transmission";
settings = { settings.incomplete-dir = "/mnt/transmission/.incomplete";
download-dir = "/media/.transmission/";
incomplete-dir = "/media/.transmission/.incomplete";
peer-port-random-low = 65500;
peer-port-random-high = 65535;
peer-port-random-on-start = true;
download-queue-enabled = false;
rpc-authentication-required = false;
rpc-bind-address = "0.0.0.0";
rpc-host-whitelist = builtins.concatStringsSep "," [host "127.0.0.1" "10.0.101.10" "10.0.${toString vm-index}.254"];
rpc-whitelist = builtins.concatStringsSep "," [host "127.0.0.1" "10.0.101.10" "10.0.${toString vm-index}.254"];
};
downloadDirPermissions = "775"; downloadDirPermissions = "775";
performanceNetParameters = true;
}; };
# debugging # debugging
@ -209,16 +169,13 @@ in {
}; };
}; };
# Add virtual hosts for all the different services running in the vm # Sonarr
services.nginx.virtualHosts = builtins.listToAttrs (pkgs.lib.attrsets.mapAttrsToList ( services.nginx.virtualHosts."sonarr.spoodythe.one" = {
name: value: {
name = "${name}.spoodythe.one";
value = {
addSSL = true; addSSL = true;
enableACME = true; enableACME = true;
listen = [ listen = [
{ {
port = value.port; inherit port;
addr = "0.0.0.0"; addr = "0.0.0.0";
ssl = false; ssl = false;
} }
@ -229,21 +186,56 @@ in {
} }
]; ];
locations."/" = { locations."/" = {
proxyPass = "http://${host}:${toString value.port}"; proxyPass = "http://${host}:${toString port}";
}; };
}; };
}
)
ports);
# Forward transmission web port to vm # Radarr
networking.nat.forwardPorts = [ services.nginx.virtualHosts."radarr.spoodythe.one" = let
port = 7878;
in {
addSSL = true;
enableACME = true;
listen = [
{ {
sourcePort = 9091; inherit port;
destination = "${host}:9091"; addr = "0.0.0.0";
ssl = false;
}
{
port = 443;
addr = "0.0.0.0";
ssl = true;
} }
]; ];
locations."/" = {
proxyPass = "http://${host}:${toString port}";
};
};
networking.firewall.allowedTCPPorts = ports-list; # Jackett
networking.firewall.allowedUDPPorts = ports-list; services.nginx.virtualHosts."jackett.spoodythe.one" = let
port = 9117;
in {
addSSL = true;
enableACME = true;
listen = [
{
inherit port;
addr = "0.0.0.0";
ssl = false;
}
{
port = 443;
addr = "0.0.0.0";
ssl = true;
}
];
locations."/" = {
proxyPass = "http://${host}:${toString port}";
};
};
networking.firewall.allowedTCPPorts = [port 9117 7878 9696 80 433];
networking.firewall.allowedUDPPorts = [port 9117 7878 9696 80 433];
} }

View file

@ -1,56 +0,0 @@
{...}: let
port = 6167;
host = "127.0.0.1";
domain = "matrix.spoodythe.one";
mb = 1024 * 1024;
max-request-size = 20;
in {
imports = [
./nginx.nix
];
services.conduwuit = {
enable = true;
settings = {
global = {
port = [port];
address = [host];
server_name = domain;
max_request_size = max-request-size * mb;
};
};
};
services.nginx.virtualHosts."${domain}" = {
forceSSL = true;
enableACME = true;
listen = [
{
port = 443;
ssl = true;
addr = "0.0.0.0";
}
{
port = 8448;
ssl = true;
addr = "0.0.0.0";
}
];
locations."/" = {
proxyPass = "http://${host}:${toString port}";
extraConfig = ''
client_max_body_size ${toString max-request-size}M;
'';
};
locations."/_matrix" = {
proxyPass = "http://${host}:${toString port}";
extraConfig = ''
client_max_body_size ${toString max-request-size}M;
'';
};
};
# Open port so i can access it on my local network
networking.firewall.allowedTCPPorts = [port 8448];
networking.firewall.allowedUDPPorts = [port 8448];
}

View file

@ -1,6 +0,0 @@
{...}: {
virtualisation.docker = {
enable = true;
storageDriver = "zfs";
};
}

View file

@ -3,34 +3,8 @@
lib, lib,
config, config,
... ...
}: let }: {
jails = { services.fail2ban = {
forgejo = mkJail "forgejo" "^.*Failed authentication attempt for .* from <ADDR>.*$";
jellyfin = mkJail "jellyfin" "^.*Authentication request for .* has been denied \\(IP: <ADDR>\\)\\.";
sonarr = mkJail "sonarr" "^.*Auth-Failure ip <ADDR> username.*$";
radarr = mkJail "sonarr" "^.*Auth-Failure ip <ADDR> username.*$";
vaultwarden = mkJail "vaultwarden" "^.*Username or password is incorrect. Try again. IP: <ADDR>\\. Username: .*$";
};
mkJail = name: filter: {
jail.settings = {
enabled = true;
filter = name;
backend = "systemd";
port = "80,443";
maxretry = 8;
bantime = "24h";
findtime = "30m";
journalmatch = "_SYSTEMD_UNIT=${name}.service";
};
filter = ''
[Definition]
failregex = ${filter}
'';
};
in {
services.fail2ban = let
in {
enable = true; enable = true;
bantime = "24h"; bantime = "24h";
@ -41,8 +15,7 @@ in {
overalljails = true; overalljails = true;
}; };
jails = jails = {
{
dovecot = lib.mkIf config.services.dovecot2.enable { dovecot = lib.mkIf config.services.dovecot2.enable {
settings = { settings = {
# block IPs which failed to log-in # block IPs which failed to log-in
@ -51,24 +24,31 @@ in {
maxretry = 3; maxretry = 3;
}; };
}; };
}
// (lib.attrsets.mapAttrs (name: value: value.jail) jails); jellyfin = lib.mkIf config.services.jellyfin.enable {
settings = {
filter = "jellyfin";
backend = "auto";
enabled = true;
port = "8096,8920";
maxretry = 3;
bantime = 86400;
findtime = 43200;
logpath = "/var/lib/jellyfin/log/*.log";
};
};
};
}; };
environment.etc = environment.etc = {
{ "fail2ban/filter.d/jellyfin.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition]
failregex = ^.*Authentication request for .* has been denied \(IP: <ADDR>\)\.
'');
# Defines a filter that detects URL probing by reading the Nginx access log # Defines a filter that detects URL probing by reading the Nginx access log
"fail2ban/filter.d/nginx-url-probe.local".text = lib.mkDefault (lib.mkAfter '' "fail2ban/filter.d/nginx-url-probe.local".text = pkgs.lib.mkDefault (pkgs.lib.mkAfter ''
[Definition] [Definition]
failregex = ^<HOST>.*(GET /(wp-|admin|boaform|phpmyadmin|\.env|\.git)|\.(dll|so|cfm|asp)|(\?|&)(=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000|=PHPE9568F36-D428-11d2-A769-00AA001ACF42|=PHPE9568F35-D428-11d2-A769-00AA001ACF42|=PHPE9568F34-D428-11d2-A769-00AA001ACF42)|\\x[0-9a-zA-Z]{2}) failregex = ^<HOST>.*(GET /(wp-|admin|boaform|phpmyadmin|\.env|\.git)|\.(dll|so|cfm|asp)|(\?|&)(=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000|=PHPE9568F36-D428-11d2-A769-00AA001ACF42|=PHPE9568F35-D428-11d2-A769-00AA001ACF42|=PHPE9568F34-D428-11d2-A769-00AA001ACF42)|\\x[0-9a-zA-Z]{2})
''); '');
} };
// (with lib.attrsets;
mapAttrs' (
name: value:
nameValuePair
"fail2ban/filter.d/${name}.local"
{text = lib.mkDefault (lib.mkAfter value.filter);}
)
jails);
} }

View file

@ -2,10 +2,6 @@
host = "127.0.0.1"; host = "127.0.0.1";
port = 6969; port = 6969;
in { in {
imports = [
./nginx.nix
./gitea-actions-runner.nix
];
services.forgejo = { services.forgejo = {
enable = true; enable = true;
@ -17,10 +13,6 @@ in {
APP_NAME = "An idiot admires complexity. A genius admires simplicity"; APP_NAME = "An idiot admires complexity. A genius admires simplicity";
}; };
repository = {
ENABLE_PUSH_CREATE_USER = true;
};
server = { server = {
DOMAIN = "git.spoodythe.one"; DOMAIN = "git.spoodythe.one";
HTTP_PORT = port; HTTP_PORT = port;
@ -46,4 +38,8 @@ in {
''; '';
}; };
}; };
# Open port 80 and 443 for reverse proxy
networking.firewall.allowedTCPPorts = [80 443];
networking.firewall.allowedUDPPorts = [80 443];
} }

View file

@ -1,93 +0,0 @@
{
config,
pkgs,
...
}: {
imports = [
./docker.nix
../modules/age.nix
];
services.gitea-actions-runner = {
package = pkgs.forgejo-runner;
instances = {
agurk = {
enable = true;
name = "agurk";
url = "http://127.0.0.1:${toString config.services.forgejo.settings.server.HTTP_PORT}";
tokenFile = config.age.secrets.forgejo-runner-token.path;
labels = [
"native:host"
];
hostPackages = pkgs.lib.attrValues {
inherit
(pkgs)
nix
nodejs
git
bash
fd
ripgrep
openssh
;
};
settings = {
log.level = "info";
runner = {
file = ".runner";
capacity = 2;
timeout = "3h";
insecure = false;
fetch_timeout = "5s";
fetch_interval = "2s";
};
};
};
hval = pkgs.lib.mkIf false {
enable = true;
name = "hval";
url = "http://host.docker.internal:${toString config.services.forgejo.settings.server.HTTP_PORT}";
tokenFile = config.age.secrets.forgejo-runner-token.path;
labels = [
docker:docker://node:16-bullseye
];
hostPackages = pkgs.lib.attrValues {
inherit
(pkgs)
nix
nodejs
git
bash
fd
ripgrep
openssh
;
};
settings = {
log.level = "info";
runner = {
file = ".runner";
capacity = 2;
timeout = "3h";
insecure = false;
fetch_timeout = "5s";
fetch_interval = "2s";
};
};
};
};
};
system.activationScripts."make-gitea-runner-dir" = pkgs.lib.stringAfter ["var"] ''
mkdir -p /var/lib/gitea-runner/
# chown -R microvm:jellyfin /var/lib/gitea-runner
'';
# systemd.services = builtins.listToAttrs (pkgs.lib.attrsets.mapAttrsToList (
# name: value: {
# name = "gitea-runner-${name}";
# value = {
# serviceConfig.ReadWritePaths = "/srv/web";
# };
# }
# ) config.services.gitea-actions-runner.instances);
}

44
services/homepage.nix Normal file
View file

@ -0,0 +1,44 @@
{...}: let
host = "127.0.0.1";
port = 8082;
in {
services.homepage-dashboard = {
enable = true;
listenPort = port;
openFirewall = false;
widgets = [
{
resources = {
cpu = true;
disk = "/";
memory = true;
};
}
];
services = [
{
"WebUI" = [
{
"Jellyfin" = {
description = "Jellyfin";
href = "https://media.spoodythe.one";
};
}
];
}
];
};
services.nginx.virtualHosts."dashboard.spoodythe.one" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://${host}:${toString port}";
};
};
# Open port 80 and 443 for reverse proxy
networking.firewall.allowedTCPPorts = [80 443];
networking.firewall.allowedUDPPorts = [80 443];
}

View file

@ -6,9 +6,35 @@
}: let }: let
host = "127.0.0.1"; host = "127.0.0.1";
port = 8096; port = 8096;
gid = 989; jellyfin =
if config.services.mullvad-vpn.enable == true
then
pkgs.callPackage ({...}:
pkgs.stdenv.mkDerivation {
pname = "jellyfin-excluded";
version = "1.0.0";
phases = ["installPhase"];
buildInputs = [pkgs.jellyfin];
# Define the install phase
installPhase = ''
mkdir -p $out/bin
# Create a wrapper script
echo "${pkgs.mullvad-vpn}/bin/mullvad-exclude ${pkgs.jellyfin}/bin/jellyfin \"$@\"" > $out/bin/jellyfin-excluded
chmod +x $out/bin/jellyfin-excluded
'';
# Specify the output
meta = with pkgs.lib; {
description = "A wrapper for the hello command";
mainProgram = "jellyfin-excluded";
license = licenses.mit;
};
}) {}
else pkgs.jellyfin;
in { in {
imports = [./nginx.nix];
# Enable VAAPI # Enable VAAPI
config.nixpkgs.config.packageOverrides = pkgs: { config.nixpkgs.config.packageOverrides = pkgs: {
vaapiIntel = pkgs.vaapiIntel.override {enableHybridCodec = true;}; vaapiIntel = pkgs.vaapiIntel.override {enableHybridCodec = true;};
@ -24,19 +50,17 @@ in {
]; ];
}; };
# Set declarative gid
config.users.groups.jellyfin.gid = gid;
# Create folder for media # Create folder for media
config.system.activationScripts."jellyfinMediaFolder" = lib.stringAfter ["var"] '' config.system.activationScripts."jellyfinMediaFolder" = lib.stringAfter ["var"] ''
mkdir -p /var/lib/media mkdir -p /media
chmod 770 /var/lib/media chmod -R 775 /media
chown -R jellyfin:jellyfin /var/lib/media chown -R jellyfin:jellyfin /media
''; '';
# Enable Jellyfin # Enable Jellyfin
config.services.jellyfin = { config.services.jellyfin = {
enable = true; enable = true;
package = jellyfin;
openFirewall = false; # We want jellyfin behind a reverse proxy openFirewall = false; # We want jellyfin behind a reverse proxy
}; };
@ -54,7 +78,6 @@ in {
enableACME = true; enableACME = true;
locations."/" = { locations."/" = {
proxyPass = "http://${host}:${toString port}"; proxyPass = "http://${host}:${toString port}";
proxyWebsockets = true;
extraConfig = '' extraConfig = ''
# Websocket support # Websocket support
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -66,7 +89,7 @@ in {
}; };
}; };
# Open ports for local network access # Open port 80 and 443 for reverse proxy
config.networking.firewall.allowedTCPPorts = [port]; config.networking.firewall.allowedTCPPorts = [port 80 443];
config.networking.firewall.allowedUDPPorts = [port]; config.networking.firewall.allowedUDPPorts = [port 80 443];
} }

View file

@ -5,12 +5,17 @@
recommendedOptimisation = true; recommendedOptimisation = true;
recommendedProxySettings = true; recommendedProxySettings = true;
recommendedTlsSettings = true; recommendedTlsSettings = true;
virtualHosts."spoodythe.one" = {
forceSSL = true;
enableACME = true;
default = true;
locations."/" = {
return = 404;
};
};
}; };
security.acme.acceptTerms = true; security.acme.acceptTerms = true;
security.acme.defaults.email = "snorre@altschul.dk"; security.acme.defaults.email = "snorre@altschul.dk";
# Open port 80 and 443 for reverse proxy
networking.firewall.allowedTCPPorts = [80 443];
networking.firewall.allowedUDPPorts = [80 443];
} }

18
services/rtorrent.nix Normal file
View file

@ -0,0 +1,18 @@
{pkgs, ...}: let
port = 9091;
in {
# services.rtorrent = {
# enable = true;
# dataDir = "/mnt/rtorrent";
# user = "rtorrent";
# group = "rtorrent";
# package = pkgs.jesec-rtorrent;
# inherit port;
# };
services.transmission = {
enable = true;
openFirewall = false;
settings.rpc-port = port;
};
}

28
services/seafile.nix Normal file
View file

@ -0,0 +1,28 @@
{
config,
pkgs,
...
}: let
host = "127.0.0.1";
port = 8008;
in {
services.seafile = {
enable = true;
adminEmail = "snorre@altschul.dk";
seahubAddress = "http://${host}:${toString port}";
# seafileSettings.fileserver = {
# inherit host port;
# };
};
services.nginx.virtualHosts."files.spoodythe.one" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://${host}:${toString port}";
};
};
# Open port 80 and 443 for reverse proxy
networking.firewall.allowedTCPPorts = [80 443];
networking.firewall.allowedUDPPorts = [80 443];
}

189
services/sonarr-old.nix Executable file
View file

@ -0,0 +1,189 @@
{
pkgs,
lib,
config,
...
}: let
host = "127.0.0.1";
port = 8989;
vm-index = 1;
vm-mac = "00:00:00:00:00:01";
vm-name = "necoarc";
in {
config.microvm.autostart = [vm-name];
config.users.extraUsers.microvm.extraGroups = [
"jellyfin" # access to media folder
];
config.system.activationScripts."make${vm-name}DataDir" = lib.stringAfter ["var"] ''
mkdir -p /var/lib/${vm-name}
chmod -R 777 /var/lib/${vm-name}
chown -R microvm /var/lib/${vm-name}
chmod -R 777 /media
'';
# config.networking.wireguard.enable = true;
# # config.boot.extraModulePackages = [config.boot.kernelPackages.wireguard];
# config.networking.wireguard.interfaces.wg0 = {
# ips = ["10.75.60.108/32"];
# listenPort = 51820;
# privateKeyFile = "${./wireguard-secret}";
# peers = [
# {
# publicKey = "TPAIPTgu9jIitgX1Bz5xMCZJ9pRRZTdtZEOIxArO0Hc=";
# endpoint = "185.254.75.4:51820";
# allowedIPs = ["0.0.0.0/0"];
# persistentKeepalive = 25;
# }
# ];
# };
# config.systemd.network.networks.wg0 = {
# matchConfig.Name = "wg0";
# address = ["10.0.1.${toString vm-index}/24"];
# networkConfig = {
# IPMasquerade = "ipv4";
# IPv4Forwarding = true;
# };
# };
config.microvm.vms.${vm-name} = {
config = {config, ...}: {
system.stateVersion = "24.11";
# Storage share configuration
microvm.shares = [
{
tag = "ro-store";
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
{
tag = "data-dir";
source = "/var/lib/${vm-name}";
mountPoint = "/mnt";
proto = "virtiofs";
}
{
tag = "media-dir";
source = "/media/shows";
mountPoint = "/media/shows";
proto = "virtiofs";
}
];
# Allow the service to use the share
system.activationScripts."chownDataDir" = lib.stringAfter ["var"] ''
mkdir -p /mnt
chmod -R 770 /mnt
chown -R sonarr:sonarr /mnt
'';
systemd.services.sonarr.serviceConfig.ExecStartPre = "/run/current-system/sw/bin/sleep 5";
systemd.services.rutorrent.serviceConfig.ExecStartPre = "/run/current-system/sw/bin/sleep 5";
microvm.hypervisor = "qemu";
# VM Networking
microvm.interfaces = [
{
id = "vm${toString vm-index}";
type = "tap";
mac = vm-mac;
}
];
networking.useNetworkd = true;
systemd.network.networks."10-eth" = {
matchConfig.MACAddress = vm-mac;
address = [
"10.0.0.${toString vm-index}/32"
];
routes = [
# Host Route
{
Destination = "10.0.0.0/32";
GatewayOnLink = true;
}
# Default route
{
Destination = "0.0.0.0/0";
Gateway = "10.0.0.0";
GatewayOnLink = true;
}
];
networkConfig = {
DNS = [
"9.9.9.9"
"8.8.8.8"
"8.8.4.4"
];
};
};
networking.useDHCP = false;
networking.nameservers = [
"10.0.101.1"
"8.8.8.8"
"8.8.4.4"
];
programs.nano.enable = lib.mkForce false;
programs.vim.enable = true;
# Services
services.sonarr = {
enable = true;
openFirewall = true;
dataDir = "/mnt/sonarr";
};
services.rtorrent = {
enable = true;
dataDir = "/mnt/rtorrent";
user = "rtorrent";
group = "rtorrent";
port = 9999;
};
# Debug user
users.users."root" = {
password = "1234";
};
environment.systemPackages = [pkgs.dig];
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "yes";
AllowUsers = null;
PasswordAuthentication = true;
KbdInteractiveAuthentication = true;
};
};
};
};
config.networking.firewall.allowedTCPPorts = [port 80 433];
config.networking.firewall.allowedUDPPorts = [port 80 433];
config.services.nginx.virtualHosts."sonarr.spoodythe.one" = {
addSSL = true;
enableACME = true;
listen = [
{
port = 8989;
addr = "0.0.0.0";
ssl = false;
}
{
port = 443;
addr = "0.0.0.0";
ssl = true;
}
];
locations."/" = {
proxyPass = "http://10.0.0.${toString vm-index}:${toString port}";
};
};
}

25
services/sonarr.nix Normal file
View file

@ -0,0 +1,25 @@
{...}: let
host = "127.0.0.1";
port = 8989;
in {
users.extraUsers.sonarr.extraGroups = ["jellyfin"]; # Access to the media folder
services.sonarr = {
enable = true;
openFirewall = true;
};
services.rtorrent = {
enable = true;
dataDir = "/mnt/rtorrent";
user = "rtorrent";
group = "rtorrent";
port = 9999;
};
services.nginx.virtualHosts."sonarr.spoodythe.one" = {
addSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://${host}:${toString port}";
};
};
}

View file

@ -2,7 +2,6 @@
host = "127.0.0.1"; host = "127.0.0.1";
port = 8222; port = 8222;
in { in {
imports = [./nginx.nix];
services.vaultwarden = { services.vaultwarden = {
enable = true; enable = true;
@ -25,6 +24,9 @@ in {
}; };
}; };
networking.firewall.allowedTCPPorts = [port];
networking.firewall.allowedUDPPorts = [port];
services.nginx.virtualHosts."vaultwarden.spoodythe.one" = { services.nginx.virtualHosts."vaultwarden.spoodythe.one" = {
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;

133
services/vm-test.nix Normal file
View file

@ -0,0 +1,133 @@
{
config,
pkgs,
...
}: let
vm-index = 1;
vm-mac = "02:00:00:00:00:02";
in {
config.microvm.vms."vm-test" = {
config = {...}: {
microvm.interfaces = [
{
id = "vm${toString vm-index}";
type = "tap";
mac = vm-mac;
}
];
microvm.shares = [
{
tag = "ro-store";
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
];
networking.useNetworkd = true;
networking.usePredictableInterfaceNames = false;
systemd.network.networks."10-eth" = {
matchConfig.MACAddress = vm-mac;
address = [
"10.0.0.${toString vm-index}/32"
];
routes = [
# Host Route
{
Destination = "10.0.0.0/32";
GatewayOnLink = true;
}
# Default route
{
Destination = "0.0.0.0/0";
Gateway = "10.0.0.0";
GatewayOnLink = true;
}
];
networkConfig = {
DNS = [
"9.9.9.9"
"8.8.8.8"
"8.8.4.4"
];
};
};
networking.useDHCP = false;
networking.nameservers = [
"10.0.101.1"
"8.8.8.8"
"8.8.4.4"
];
systemd.services."wireguard-kill-switch" = {
description = "Wireguard Kill Switch";
after = ["network-online.target"];
wants = ["network-online.target"];
wantedBy = ["multi-user.target"];
serviceConfig = {
type = "oneshot";
ExecStart = pkgs.writeShellScript "wgconf.sh" ''
# Stay a while and listen
# ${pkgs.toybox}/bin/sleep 5
# Route local traffic through wg0 except local traffic
${pkgs.iproute2}/bin/ip route add 10.0.0.0/32 dev eth0 && \
${pkgs.iproute2}/bin/ip route add 0.0.0.0/1 dev wg0
# Block all traffic that isnt local or through the vpn
${pkgs.iptables}/bin/iptables -I OUTPUT ! -o wg0 -m mark ! --mark 42 -m addrtype ! --dst-type LOCAL ! -d 10.0.0.0/32 -j REJECT
'';
RemainAfterExit = "yes";
};
};
networking.wireguard.enable = true;
systemd.network = {
netdevs."10-wg0" = {
netdevConfig = {
Kind = "wireguard";
Name = "wg0";
MTUBytes = "1300";
};
wireguardConfig = {
PrivateKeyFile = "${./wireguard-secret}";
FirewallMark = 42;
ListenPort = 51820;
};
wireguardPeers = [
{
PublicKey = "0qSP0VxoIhEhRK+fAHVvmfRdjPs2DmmpOCNLFP/7cGw=";
AllowedIPs = ["0.0.0.0/0"];
Endpoint = "193.32.248.66:51820";
# PersistentKeepalive = 25;
}
];
};
networks."wg0" = {
matchConfig.Name = "wg0";
address = [
" 10.65.241.123/32"
];
DHCP = "no";
dns = ["10.64.0.1"];
gateway = [
"10.0.0.0"
];
};
};
users.users.root = {
password = "1234";
};
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "yes";
AllowUsers = null;
PasswordAuthentication = true;
KbdInteractiveAuthentication = true;
};
};
};
};
}

4
services/vpn.nix Normal file
View file

@ -0,0 +1,4 @@
{pkgs, ...}: {
services.mullvad-vpn.enable = true;
environment.systemPackages = [pkgs.wireguard-tools];
}

View file

@ -5,77 +5,12 @@
}: let }: let
host = "127.0.0.1"; host = "127.0.0.1";
port = 8080; port = 8080;
user = "website-host-user";
repo = "https://git.spoodythe.one/spoody/website";
in { in {
imports = [./nginx.nix];
services.nginx.virtualHosts."spoodythe.one" = { services.nginx.virtualHosts."spoodythe.one" = {
enableACME = true; enableACME = true;
forceSSL = true; forceSSL = true;
default = true;
locations."/" = { locations."/" = {
proxyPass = "http://${host}:${toString port}"; proxyPass = "http://${host}:${toString port}";
}; };
}; };
users.users."${user}" = {
isSystemUser = true;
home = "/home/${user}";
createHome = true;
group = "${user}";
shell = pkgs.bash;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG90JNokb4/4DZ/2tHS8Lj/jq+UiA0B2g+MFeM8KuA6Y website-host-user@server"
];
};
users.groups."${user}" = {};
security.sudo.extraRules = [
{
users = ["${user}"];
commands = [
{
command = "/run/current-system/sw/bin/systemctl restart rebuild-website.service";
options = ["SETENV" "NOPASSWD"];
}
{
command = "/run/current-system/sw/bin/systemd-run -d --uid website-host-user ./webbisitey";
options = ["SETENV" "NOPASSWD"];
}
];
}
];
systemd.services."rebuild-website" = {
description = "Service for running my website";
wants = ["network-online.target"];
after = ["network-online.target"];
wantedBy = ["multi-user.target"];
script = ''
echo "Sleeping for 25 seconds to allow forgejo to start"
${pkgs.busybox}/bin/sleep 25
echo "Making temporary folder"
path=$(mktemp -d)
echo "Cloning repo"
${pkgs.git}/bin/git clone ${repo} "$path"
echo "cd $path"
cd "$path"
echo "[nix build] Rebuilding website flake..."
${pkgs.nix}/bin/nix build # build
echo "[killall] Killing previous website process"
${pkgs.killall}/bin/killall webbisitey || true # stop old website
echo "Changing directory to build result..."
cd result/bin # cd into result folder
echo "[systemd-run] Running webbisitey-wrapped..."
/run/wrappers/bin/sudo /run/current-system/sw/bin/systemd-run -d --uid ${user} ./webbisitey # run new website
'';
serviceConfig = {
Type = "oneshot";
RemainsAfterExit = true;
User = user;
WorkingDirectory = "/tmp";
};
};
} }

View file

@ -1,11 +1,11 @@
{pkgs, ...}: { {...}: {
services.zfs.zed = { services.zfs.zed = {
enableMail = true; enableMail = true;
settings = { settings = {
ZED_DEBUG_LOG = "/tmp/zed.debug.log"; ZED_DEBUG_LOG = "/tmp/zed.debug.log";
ZED_EMAIL_ADDR = "snorre@altschul.dk"; ZED_EMAIL_ADDR = "snorre@altschul.dk";
ZED_EMAIL_PROG = "${pkgs.postfix}/bin/sendmail"; ZED_EMAIL_PROG = "sendmail";
ZED_EMAIL_OPTS = " @ADDRESS@"; ZED_EMAIL_OPTS = "-s '@SUBJECT@' @ADDRESS@";
ZED_LOCKDIR = "/var/lock"; ZED_LOCKDIR = "/var/lock";
ZED_NOTIFY_INTERVAL_SECS = 3600; ZED_NOTIFY_INTERVAL_SECS = 3600;