From 4014d5e658926dc3030d72a6b78921df81f3f9f5 Mon Sep 17 00:00:00 2001 From: Squibid Date: Sun, 9 Nov 2025 23:51:40 -0500 Subject: [PATCH] initial commit --- .sops.yaml | 22 ++++++ Makefile | 21 ++++++ README.md | 40 ++++++++++ flake.nix | 56 ++++++++++++++ hosts/blobercraft/ai.nix | 34 +++++++++ hosts/blobercraft/default.nix | 17 +++++ hosts/blobercraft/gatus.nix | 45 +++++++++++ hosts/blobercraft/hardware-configuration.nix | 39 ++++++++++ hosts/blobercraft/jellyfin.nix | 78 ++++++++++++++++++++ hosts/blobercraft/minecraft.nix | 24 ++++++ hosts/crayon/default.nix | 12 +++ hosts/crayon/git.nix | 32 ++++++++ hosts/crayon/hardware-configuration.nix | 32 ++++++++ hosts/crayon/mailserver.nix | 41 ++++++++++ hosts/crayon/nginx.nix | 46 ++++++++++++ hosts/crayon/www/5438.squi.bid.nix | 12 +++ hosts/crayon/www/squi.bid.nix | 31 ++++++++ hosts/crayon/www/voidpkgs.squi.bid.nix | 17 +++++ modules/os.nix | 26 +++++++ modules/pkgs.nix | 18 +++++ modules/sops.nix | 22 ++++++ modules/ssh.nix | 28 +++++++ modules/time.nix | 4 + modules/unstable.nix | 9 +++ modules/users/admin.nix | 32 ++++++++ modules/zmotd.nix | 62 ++++++++++++++++ overlays/default.nix | 20 +++++ overlays/zmotd/build.zig.zon.nix | 21 ++++++ overlays/zmotd/default.nix | 26 +++++++ secrets.yaml | 44 +++++++++++ 30 files changed, 911 insertions(+) create mode 100644 .sops.yaml create mode 100644 Makefile create mode 100644 README.md create mode 100644 flake.nix create mode 100644 hosts/blobercraft/ai.nix create mode 100644 hosts/blobercraft/default.nix create mode 100644 hosts/blobercraft/gatus.nix create mode 100644 hosts/blobercraft/hardware-configuration.nix create mode 100644 hosts/blobercraft/jellyfin.nix create mode 100644 hosts/blobercraft/minecraft.nix create mode 100644 hosts/crayon/default.nix create mode 100644 hosts/crayon/git.nix create mode 100644 hosts/crayon/hardware-configuration.nix create mode 100644 hosts/crayon/mailserver.nix create mode 100644 hosts/crayon/nginx.nix create mode 100644 hosts/crayon/www/5438.squi.bid.nix create mode 100644 hosts/crayon/www/squi.bid.nix create mode 100644 hosts/crayon/www/voidpkgs.squi.bid.nix create mode 100644 modules/os.nix create mode 100644 modules/pkgs.nix create mode 100644 modules/sops.nix create mode 100644 modules/ssh.nix create mode 100644 modules/time.nix create mode 100644 modules/unstable.nix create mode 100644 modules/users/admin.nix create mode 100644 modules/zmotd.nix create mode 100644 overlays/default.nix create mode 100644 overlays/zmotd/build.zig.zon.nix create mode 100644 overlays/zmotd/default.nix create mode 100644 secrets.yaml diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..6483a32 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,22 @@ +# The .sops.yaml basically explains who can decrypt the secrets.yaml file which +# contains all the goodies. The age encrypted values below are pubkeys in an +# age keypair. You will still need to put the appropriate private key on the +# system to decrypt the secrets. Ideally you're using the ssh keys already +# setup on the server to convert to an age key that way you don't have to +# bootstrap at all. +keys: + # make sure to run `sops updatekeys secrets.yaml` after changing the keys + - &users: + - &dev age14d55nfxlzm8t2yzplxpprygxmt99javafz9a8dh5llu87aww4qlswf6g0c + - &hosts: + - &dev-vm age1rjtqzmywfr3zuzz0cn8eqnwp3x8ypzya9gcv6kvtplhudar5eayqq83ey4 + - &crayon age1pnu4tkdxfcnefntdw262k4m8wuv3qe2894s4e6w5j8yshg8vlu6q9uq5tv + # - &blobercraft +creation_rules: + - path_regex: secrets.yaml$ + key_groups: + - age: + - *dev + - *dev-vm + - *crayon + # - *blobercraft diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f4bb098 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +IP ?= +HOST ?= +deploy: + # push flake config to a remote server +ifeq ($(IP),) + $(error IP not set) +endif +ifeq ($(HOST),) + $(error HOST not set) +endif + rsync -azr ./ crown@$(IP):~/flake-config + ssh crown@$(IP) "sudo nixos-rebuild switch --flake ~/flake-config#$(HOST)" + +sops: + # update sops keys + sops updatekeys secrets.yaml + +.DEFAULT_GOAL := default +.PHONY: default deploy sops +default: + # noop diff --git a/README.md b/README.md new file mode 100644 index 0000000..639c747 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# My servers using nixos +All of the different hosts (machines) are stored in the `hosts` directory. +Modules used across different hosts are stored in the `modules` directory. +The `overlays` directory contains programs that I've packaged for myself. +That's it, have fun poking around. + +## Systems +I suppose if you're trying to understand my config you'd need to know where +this stuff is deployed and what for. +- blobercraft + - This is my main homelab server. It's used for my media (jellyfin) along + with some other stuff that you can find by looking at my config. +- crayon + - My vps running somewhere. It hosts my email, git, and site. Along with + whatever else I've decided I want to be publicly facing. + +If there's something that's in a specific host's directory then it's only used +for that host, everything shared between them is likely specified in the +`flake.nix` file. I've tried to add a reasonable amount of comments for things +that I thought a new user might want to understand as nix is rather poorly +documented. + +## Maintaining +I've included a make file to keep remote systems up to date, here's an example +of what I use to deploy to crayon: +```sh +make deploy IP=squi.bid HOST=crayon +``` +This step requires the remote system to have a crown user who can execute sudo +commands without an interactive prompt. + +I'll probably end up switching to something more standard once I've got the +time. + +## TODO: +- [ ] blobercraft + - [ ] add a git backup for everything on crayon (if possible) + - [ ] ff sync server +- [ ] crayon + - [ ] find a way to make my site deploy declaratively diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c26540a --- /dev/null +++ b/flake.nix @@ -0,0 +1,56 @@ +{ + description = "Nixos config flake"; + inputs = { + nixpkgs.url = "nixpkgs/nixos-25.05"; + unstable.url = "nixpkgs/nixos-unstable"; + + nid.url = "github:nix-community/nix-index-database"; + nid.inputs.nixpkgs.follows = "nixpkgs"; + + sops-nix.url = "github:Mic92/sops-nix"; + sops-nix.inputs.nixpkgs.follows = "nixpkgs"; + + declarative-jellyfin.url = "github:Sveske-Juice/declarative-jellyfin"; + declarative-jellyfin.inputs.nixpkgs.follows = "nixpkgs"; + }; + outputs = { self, nixpkgs, unstable, ... }@inputs: let + base = [ + # I've put these all here so that it's easier to see what's being + # imported by default + ./modules/os.nix + ./modules/ssh.nix + ./modules/time.nix + ./modules/pkgs.nix + ./modules/unstable.nix + ./modules/zmotd.nix + ./modules/sops.nix + ./modules/users/admin.nix + ./overlays + + { services.zmotd.enable = true; } # enable my motd service + { programs.nix-ld.enable = true; } # use nix-ld cause nixos is a bit dumb + inputs.declarative-jellyfin.nixosModules.default # jellyfin :) + # use comma just in case I need to do some sysadmin stuff + inputs.nid.nixosModules.nix-index + { programs.nix-index-database.comma.enable = true; } + ]; + + # ts so DRY it makes me wanna cry + mkHosts = hosts: + (builtins.mapAttrs (name: modules: + nixpkgs.lib.nixosSystem { + specialArgs = { inherit inputs; }; + modules = base ++ [ + { networking.hostName = name; } + ./hosts/${name} # just specifying a directory uses default.nix + ] ++ modules; + } + )) <| hosts; + in { + # define all of my machines + nixosConfigurations = mkHosts { + blobercraft = []; + crayon = []; + }; + }; +} diff --git a/hosts/blobercraft/ai.nix b/hosts/blobercraft/ai.nix new file mode 100644 index 0000000..961752d --- /dev/null +++ b/hosts/blobercraft/ai.nix @@ -0,0 +1,34 @@ +{ lib, config, ... }: +{ + options.ai.enable = lib.mkEnableOption "enable ai services"; + config = lib.mkIf config.ai.enable { + fileSystems."/mnt/priv" = { + device = "192.168.50.240:/mnt/tank/Private"; + fsType = "nfs"; + options = [ "defaults" ]; + }; + + services.gatus.settings.endpoints = [ + { + name = "open-webui"; + group = "local"; + url = "http://0.0.0.0:${config.services.open-webui.port}/System/Ping"; + interval = "5m"; + # conditions = [''[BODY] == "Jellyfin Server"'']; # TODO: + } + ]; + + services = { + ollama = { + enable = true; + # Optional: preload models, see https://ollama.com/library + loadModels = [ "llama3.2:3b" "deepseek-r1:1.5b"]; + }; + open-webui = { + enable = true; + port = 2333; + openFirewall = true; + }; + }; + }; +} diff --git a/hosts/blobercraft/default.nix b/hosts/blobercraft/default.nix new file mode 100644 index 0000000..07474e3 --- /dev/null +++ b/hosts/blobercraft/default.nix @@ -0,0 +1,17 @@ +{ ... }: +{ + imports = [ + ./hardware-configuration.nix # Include the results of the hardware scan. + ./jellyfin.nix + ./minecraft.nix + ./gatus.nix + ./ai.nix + ]; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + # ai.enable = true; + jellyfin.enable = true; + minecraft.enable = true; +} diff --git a/hosts/blobercraft/gatus.nix b/hosts/blobercraft/gatus.nix new file mode 100644 index 0000000..fd459ce --- /dev/null +++ b/hosts/blobercraft/gatus.nix @@ -0,0 +1,45 @@ +{ unstable, ... }: let + gatus.up = [ + "[STATUS] == 200" + "[RESPONSE_TIME] < 300" + ]; +in { + services.gatus = { + package = unstable.gatus; + enable = true; + openFirewall = true; + settings = { + web.port = 8081; + endpoints = [ + { + name = "nas"; + group = "external"; + url = "http://192.168.50.240"; + interval = "5m"; + conditions = gatus.up; + } + { + name = "site"; + group = "remote"; + url = "https://squi.bid"; + interval = "10m"; + conditions = gatus.up; + } + { + name = "git site"; + group = "remote"; + url = "https://git.squi.bid"; + interval = "10m"; + conditions = gatus.up; + } + { + name = "voidpkgs"; + group = "remote"; + url = "https://voidpkgs.squi.bid"; + interval = "10m"; + conditions = [''[BODY] == pat(*x86_64-repodata.sig2*)'']; + } + ]; + }; + }; +} diff --git a/hosts/blobercraft/hardware-configuration.nix b/hosts/blobercraft/hardware-configuration.nix new file mode 100644 index 0000000..8c24f26 --- /dev/null +++ b/hosts/blobercraft/hardware-configuration.nix @@ -0,0 +1,39 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/37cd6e5e-5e67-48de-a2cf-9f1f26db5721"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/2EB4-8533"; + fsType = "vfat"; + options = [ "fmask=0077" "dmask=0077" ]; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/7849db93-3c39-4571-ac39-8542251eb194"; } + ]; + + # 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..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/hosts/blobercraft/jellyfin.nix b/hosts/blobercraft/jellyfin.nix new file mode 100644 index 0000000..da82671 --- /dev/null +++ b/hosts/blobercraft/jellyfin.nix @@ -0,0 +1,78 @@ +{ lib, config, ... }: +{ + options.jellyfin.enable = lib.mkEnableOption "enable jellfin service"; + config = lib.mkIf config.jellyfin.enable { + fileSystems."/mnt/media" = { + device = "192.168.50.240:/mnt/tank/Media"; + fsType = "nfs"; + options = [ "defaults" ]; + }; + + services.gatus.settings.endpoints = [ + { + name = "jellyfin"; + group = "local"; + url = "http://localhost:8096/System/Ping"; + interval = "5m"; + conditions = [''[BODY] == "Jellyfin Server"'']; + } + ]; + + services.declarative-jellyfin = { + enable = true; + openFirewall = true; + serverId = "0ba4e888503b4524a90285b7ad500256"; # could be anything + system = { + serverName = config.networking.hostName; + trickplayOptions = { + enableHwAcceleration = true; + enableHwEncoding = true; + }; + pluginRepositories = [ + { + content.Name = "Jellyfin Stable"; + content.Url = "https://repo.jellyfin.org/files/plugin/manifest.json"; + tag = "RepositoryInfo"; # Needed to generate the correct XML + } + { + content.Name = "Intro Skipper"; + content.Url = "https://intro-skipper.org/manifest.json"; + tag = "RepositoryInfo"; # Needed to generate the correct XML + } + ]; + }; + users.zachary = { + mutable = false; + permissions.isAdministrator = true; + hashedPasswordFile = config.sops.secrets."jellyfin/zachary".path; + }; + libraries = { + Movies = { + enabled = true; + contentType = "movies"; + pathInfos = ["/mnt/media/movies"]; + }; + Shows = { + enabled = true; + contentType = "tvshows"; + pathInfos = ["/mnt/media/shows"]; + }; + }; + encoding = { + enableHardwareEncoding = true; + hardwareAccelerationType = "vaapi"; + enableDecodingColorDepth10Hevc = true; # enable if your system supports + allowHevcEncoding = true; # enable if your system supports + allowAv1Encoding = true; # enable if your system supports + hardwareDecodingCodecs = [ # enable the codecs your system supports + "h264" + "hevc" + "mpeg2video" + "vc1" + "vp9" + "av1" + ]; + }; + }; + }; +} diff --git a/hosts/blobercraft/minecraft.nix b/hosts/blobercraft/minecraft.nix new file mode 100644 index 0000000..d5f28e8 --- /dev/null +++ b/hosts/blobercraft/minecraft.nix @@ -0,0 +1,24 @@ +{ lib, config, pkgs, ... }: +{ + options.minecraft.enable = lib.mkEnableOption "enable minecraft user"; + config = lib.mkIf config.minecraft.enable { + users.users.minecraft = { + createHome = true; + home = "/home/minecraft"; + useDefaultShell = true; + isNormalUser = true; + description = "minecraft server account"; + group = "minecraft"; + openssh.authorizedKeys.keys = [] ++ config.ssh.keys; + + # make sure we have every version of java required to run minecraft + packages = with pkgs; [ + jre8 + jre17_minimal + jre21_minimal + ]; + }; + + users.groups.minecraft = {}; + }; +} diff --git a/hosts/crayon/default.nix b/hosts/crayon/default.nix new file mode 100644 index 0000000..c16c088 --- /dev/null +++ b/hosts/crayon/default.nix @@ -0,0 +1,12 @@ +{ ... }: +{ + imports = [ + ./hardware-configuration.nix # Include the results of the hardware scan. + ./mailserver.nix + ./nginx.nix + ./git.nix + ]; + + boot.loader.grub.enable = true; + boot.loader.grub.device = "/dev/vda"; +} diff --git a/hosts/crayon/git.nix b/hosts/crayon/git.nix new file mode 100644 index 0000000..ef05b6d --- /dev/null +++ b/hosts/crayon/git.nix @@ -0,0 +1,32 @@ +{ config, ... }: +let + cfg = config.services.forgejo; + srv = cfg.settings.server; +in { + services.nginx.virtualHosts.${srv.DOMAIN} = { + forceSSL = true; + enableACME = true; + extraConfig = '' + client_max_body_size 512M; + ''; + locations."/".proxyPass = "http://localhost:${toString srv.HTTP_PORT}"; + }; + + services.forgejo = { + enable = true; + database.type = "postgres"; + lfs.enable = true; + settings = { + server = { + DOMAIN = "git.squi.bid"; + ROOT_URL = "https://${srv.DOMAIN}/"; + HTTP_PORT = 3000; + }; + service = { + ENABLE_CAPTCHA = true; + REGISTER_MANUAL_CONFIRM = true; # all new users must be approved by me + }; + ui.DEFAULT_THEME = "gitea-dark"; + }; + }; +} diff --git a/hosts/crayon/hardware-configuration.nix b/hosts/crayon/hardware-configuration.nix new file mode 100644 index 0000000..3df7975 --- /dev/null +++ b/hosts/crayon/hardware-configuration.nix @@ -0,0 +1,32 @@ +# 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 = [ ]; + + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/4d47e9c5-a695-4c12-b44c-1c3c81de20d4"; + fsType = "ext4"; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/937d42d7-9d77-46fd-88fb-3d6d746635ed"; } + ]; + + # 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..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + virtualisation.hypervGuest.enable = true; +} diff --git a/hosts/crayon/mailserver.nix b/hosts/crayon/mailserver.nix new file mode 100644 index 0000000..e9847a5 --- /dev/null +++ b/hosts/crayon/mailserver.nix @@ -0,0 +1,41 @@ +{ config, ... }: +{ + # this should really be imported through a flake but I couldn't get that + # working :( + imports = [ + (builtins.fetchTarball { + url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-25.05/nixos-mailserver-nixos-25.05.tar.gz"; + sha256 = "1qn5fg0h62r82q7xw54ib9wcpflakix2db2mahbicx540562la1y"; + }) + ]; + + mailserver = { + enable = true; + fqdn = "mail.zacharyscheiman.com"; + domains = [ "zacharyscheiman.com" "squi.bid" ]; + messageSizeLimit = 2500000000; # 2.5GB + + loginAccounts = { + "me@zacharyscheiman.com" = { + hashedPasswordFile = config.sops.secrets."mail/me".path; + aliases = [ + "zach@zacharyscheiman.com" + "zack@zacharyscheiman.com" + "zachary@zacharyscheiman.com" + + # required aliases + "postmaster@zacharyscheiman.com" + "abuse@zacharyscheiman.com" + "security@zacharyscheiman.com" + ]; + }; + }; + + # Use Let's Encrypt certificates. Note that this needs to set up a stripped + # down nginx and opens port 80. + certificateScheme = "acme-nginx"; + }; + + security.acme.acceptTerms = true; + security.acme.defaults.email = "security@zacharyscheiman.com"; +} diff --git a/hosts/crayon/nginx.nix b/hosts/crayon/nginx.nix new file mode 100644 index 0000000..210d015 --- /dev/null +++ b/hosts/crayon/nginx.nix @@ -0,0 +1,46 @@ +{ pkgs, config, ... }: +let + mkVirtHosts = virtHosts: + builtins.listToAttrs (builtins.map (name: { + name = name; + value = (builtins.import ./www/${name}.nix { + # we have to explicitly pass in arguments because we're using import + phpsock = config.services.phpfpm.pools.nginx.socket; + inherit pkgs; + }); + }) <| virtHosts); +in { + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + # setup phpfpm pooler for sites using php + services.phpfpm.pools = { + nginx = { + user = config.services.nginx.user; + group = config.services.nginx.group; + phpPackage = pkgs.php; + settings = { + "listen.owner" = config.services.nginx.user; + "listen.group" = config.services.nginx.group; + "listen.mode" = "0660"; + "pm" = "dynamic"; + "pm.max_children" = 5; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 3; + }; + }; + }; + + services.nginx = { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + + virtualHosts = mkVirtHosts [ + "squi.bid" + "5438.squi.bid" + "voidpkgs.squi.bid" + ]; + }; +} diff --git a/hosts/crayon/www/5438.squi.bid.nix b/hosts/crayon/www/5438.squi.bid.nix new file mode 100644 index 0000000..cd630c5 --- /dev/null +++ b/hosts/crayon/www/5438.squi.bid.nix @@ -0,0 +1,12 @@ +{ ... }: +{ + root = "/var/www/5438"; # TODO: make declarative + + locations."/" = { + index = "zacharys-guide.pdf"; + }; + + # https + enableACME = true; + forceSSL = true; +} diff --git a/hosts/crayon/www/squi.bid.nix b/hosts/crayon/www/squi.bid.nix new file mode 100644 index 0000000..9ac1c04 --- /dev/null +++ b/hosts/crayon/www/squi.bid.nix @@ -0,0 +1,31 @@ +{ phpsock, pkgs, ... }: +{ + serverAliases = ["www.squi.bid"]; + root = "/var/www/squi.bid"; # TODO: make declarative + + locations = { + "/" = { + tryFiles = "$uri $uri.html $uri/ @extensionless-php"; + index = "index.html index.htm index.php"; + }; + "~ \\.php$" = { + extraConfig = '' + fastcgi_pass unix:${phpsock}; + include ${pkgs.nginx}/conf/fastcgi.conf; + expires 3m; + add_header Cache-Control "max-age=180, public"; + ''; + }; + "~* \\.(?:css|js|jpg|png)$" = { + extraConfig = '' + expires 1y; + access_log off; + add_header Cache-Control "max-age=31556952, public"; + ''; + }; + }; + + # https + enableACME = true; + forceSSL = true; +} diff --git a/hosts/crayon/www/voidpkgs.squi.bid.nix b/hosts/crayon/www/voidpkgs.squi.bid.nix new file mode 100644 index 0000000..d2894f0 --- /dev/null +++ b/hosts/crayon/www/voidpkgs.squi.bid.nix @@ -0,0 +1,17 @@ +{ ... }: +{ + root = "/var/www/voidpkgs"; # TODO: make declarative + + locations."/" = { + extraConfig = '' + try_files $uri $uri/ =404; + sendfile on; + sendfile_max_chunk 1m; + autoindex on; + ''; + }; + + # https + enableACME = true; + forceSSL = true; +} diff --git a/modules/os.nix b/modules/os.nix new file mode 100644 index 0000000..c1d5a86 --- /dev/null +++ b/modules/os.nix @@ -0,0 +1,26 @@ +{ + # configure nix stuff + nix = { + settings = { + experimental-features = [ "nix-command" "flakes" "pipe-operators" ]; + auto-optimise-store = true; + }; + gc = { + dates = "weekly"; + automatic = true; + randomizedDelaySec = "45min"; + }; + }; + + # Make sure the system is at least semi up to date + system.autoUpgrade = { + enable = true; + dates = "weekly"; + }; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "25.05"; # Did you read the comment? +} diff --git a/modules/pkgs.nix b/modules/pkgs.nix new file mode 100644 index 0000000..59d3a48 --- /dev/null +++ b/modules/pkgs.nix @@ -0,0 +1,18 @@ +{ pkgs, ... }: +{ + # just a bunch of default packages that I use + environment.systemPackages = with pkgs; [ + curl + git + htop + neovim + progress + tmux + tree + unzip + zmotd + ]; + + security.sudo.execWheelOnly = true; + security.sudo.wheelNeedsPassword = false; +} diff --git a/modules/sops.nix b/modules/sops.nix new file mode 100644 index 0000000..0784e45 --- /dev/null +++ b/modules/sops.nix @@ -0,0 +1,22 @@ +{ inputs, ... }: +{ + imports = [ inputs.sops-nix.nixosModules.sops ]; + + sops = { + defaultSopsFile = ../secrets.yaml; + validateSopsFiles = false; + + # Derive the age key from the systems ssh key. I didn't know this before but + # it seems like all systems have ssh keys already generated. + age = { + sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; + keyFile = "/var/lib/sops-nix/key.txt"; + generateKey = true; + }; + + secrets = { + "mail/me" = {}; + "jellyfin/zachary" = {}; + }; + }; +} diff --git a/modules/ssh.nix b/modules/ssh.nix new file mode 100644 index 0000000..a04ab33 --- /dev/null +++ b/modules/ssh.nix @@ -0,0 +1,28 @@ +{ lib, config, ... }: +{ + options.ssh = { + disable = lib.mkOption { + default = false; + type = lib.types.bool; + description = "disable ssh conifguration"; + }; + keys = lib.mkOption { + type = lib.types.listOf lib.types.str; + # A list of my keys so I can access my servers. This also allows me into + # root on every system using this module. + default = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJUvmBJcNTniRgcFj36vWVTlJHEL5JS8wAst65Tx+mLpD69QxGsaXU/xvluX7xl+gnAKM2mZrG8oGPCBxsug1VytHLthUHlDqSPa/iSLwHq2KoUlSMvLR6iVzZiBLc+x8+jXyDL0QE4cEPKSbKsPBqSwMyWVZLy3dpG4WuOhD6MkJ9+hsHvzfHR67bNkl8zuB2ghj4TiUHgW47JBSXaIEXzevDiVUfhH/bZV2UFXaAcl2UesqcEX5BS1/w0RrHpDZN6NZFjdu76ltXw9zR8qwSihHOaw0HOpN3ikw87ENE15DqEDBcC9bl+xklMfbGj3vgGitj3W7VfbUFwabyTb/M7e8e5jZlZ2Jdyx31xnoxSV2Mb2j+ZRVvzux4S4If5EJtkiR1i/fxjIS3Uf4lDa4lGy1lqKTDjrkuDYA2xoh+eA5PgLUFw1h1sROzerqarmVZAfyuvqnzO82xE39ea8zK9PeenVGjXn+USC1+ueGPvtlhmeKvIUUugNp3g2rciKM= squibid@vrooom" + ]; + }; + }; + + config = lib.mkIf (!config.ssh.disable) { + services.sshguard.enable = true; + services.openssh = { + enable = true; + settings.PasswordAuthentication = false; + }; + + users.users.root.openssh.authorizedKeys.keys = config.ssh.keys; + }; +} diff --git a/modules/time.nix b/modules/time.nix new file mode 100644 index 0000000..9ed4098 --- /dev/null +++ b/modules/time.nix @@ -0,0 +1,4 @@ +{ + services.ntp.enable = true; + time.timeZone = "America/New_York"; +} diff --git a/modules/unstable.nix b/modules/unstable.nix new file mode 100644 index 0000000..dffb66f --- /dev/null +++ b/modules/unstable.nix @@ -0,0 +1,9 @@ +{ inputs, pkgs, ... }: +{ + # this is quite silly but it works and with it you can specify packages to + # come from unstable like so: + # unstable.my_package_name + _module.args.unstable = import inputs.unstable { + inherit (pkgs.stdenv.hostPlatform) system; + }; +} diff --git a/modules/users/admin.nix b/modules/users/admin.nix new file mode 100644 index 0000000..8e52ed9 --- /dev/null +++ b/modules/users/admin.nix @@ -0,0 +1,32 @@ +{ lib, config, ... }: +{ + imports = [ ../ssh.nix ]; + + options.admin = { + disable = lib.mkOption { + default = false; + type = lib.types.bool; + description = "disable admin user"; + }; + }; + + # named this way to reduce the attack surface of my servers + config = lib.mkIf (!config.admin.disable) { + sops.secrets."users/crown".neededForUsers = true; + users.mutableUsers = false; # required for sops to touch the password + + users.users.crown = { + description = "wikipedia.org/wiki/Root_crown"; + home = "/home/crown"; + createHome = true; + group = "crown"; + extraGroups = [ "wheel" ]; + useDefaultShell = true; + isNormalUser = true; + hashedPasswordFile = config.sops.secrets."users/crown".path; + openssh.authorizedKeys.keys = config.ssh.keys; + }; + + users.groups.crown = {}; + }; +} diff --git a/modules/zmotd.nix b/modules/zmotd.nix new file mode 100644 index 0000000..2be27c6 --- /dev/null +++ b/modules/zmotd.nix @@ -0,0 +1,62 @@ +{ lib, pkgs, config, ... }: +{ + options.services.zmotd = { + enable = lib.mkEnableOption "zmotd"; + interval = lib.mkOption { + default = "5m"; + example = "5m"; + description = "Change the timing of zmotd runs."; + type = lib.types.str; + }; + config = lib.mkOption { + default = { + entries = [ + { m = "hostname"; f = "fig"; } + { m = "distro"; } + { m = "kernel"; } + { m = "load"; } + { m = "uptime"; } + ]; + }; + type = lib.types.attrsOf lib.types.anything; + }; + motdFile = lib.mkOption { + default = "/etc/motd"; + description = "Change the where zmotd outputs to."; + }; + }; + + config = lib.mkIf config.services.zmotd.enable { + users.motdFile = config.services.zmotd.motdFile; + systemd.services.zmotd = { + description = "Generate dynamic MOTD using zmotd"; + enable = config.services.zmotd.enable; + + serviceConfig = { + Type = "oneshot"; + User = "root"; + Group = "root"; + ExecStart = let + motdFile = config.services.zmotd.motdFile; + configFile = pkgs.writers.writeTOML + "zmotd-config.toml" + config.services.zmotd.config; + in pkgs.writeShellScript "zmotd-wrapper" '' + ${pkgs.zmotd}/bin/zmotd ${configFile} > ${motdFile} + ''; + }; + }; + systemd.timers.zmotd = { + description = "Regenerate MOTD often"; + enable = config.services.zmotd.enable; + + wantedBy = [ "timers.target" ]; + timerConfig = { + OnActiveSec = "0s"; + OnUnitActiveSec = config.services.zmotd.interval; + Unit = "zmotd.service"; + Persistent = true; + }; + }; + }; +} diff --git a/overlays/default.nix b/overlays/default.nix new file mode 100644 index 0000000..e65e67d --- /dev/null +++ b/overlays/default.nix @@ -0,0 +1,20 @@ +# Loop through all files and directories and try and import them as nixpkgs +# overlays. I've given them access to nixpkgs unstable just incase ;) +{ lib, unstable, ... }: +let +files = builtins.readDir ./.; +import_overlay = i: map (p: builtins.import (./. + ("/" + p)) { inherit unstable; }) i; +in { + nixpkgs.overlays = + (files # files + |> lib.filterAttrs (n: v: v == "regular") + |> builtins.attrNames + |> builtins.filter (inp: inp != "default.nix" + && (builtins.match ".*\\.nix$" inp) != null) + |> import_overlay) + ++ + (files # directories + |> lib.filterAttrs (n: v: v == "directory") + |> builtins.attrNames + |> import_overlay); +} diff --git a/overlays/zmotd/build.zig.zon.nix b/overlays/zmotd/build.zig.zon.nix new file mode 100644 index 0000000..b86432a --- /dev/null +++ b/overlays/zmotd/build.zig.zon.nix @@ -0,0 +1,21 @@ +# generated by zon2nix (https://github.com/nix-community/zon2nix) + +{ linkFarm, fetchzip, fetchgit }: + +linkFarm "zig-packages" [ + { + name = "toml-0.3.0-bV14BfV7AQD8DkuQI7skP8ekQTaBYKTO0MY_35Cw_EXo"; + path = fetchgit { + url = "https://github.com/sam701/zig-toml"; + rev = "475b03c630c802f8b6bd3e239d8fc2279b4fadb8"; + hash = "sha256-TOW27uDzos3P0K3535TYxogR1o2j5u2BaMHo9vyAbW8="; + }; + } + { + name = "ziglet-0.1.0-QYl7SRPiAAAW4knyR898VHRmXzPcN25LK6b4tiDIkvSY"; + path = fetchzip { + url = "https://codeberg.org/benteg/ziglet/archive/5366b65794d42f658da3d171eaa3321fafa2b52f.tar.gz"; + hash = "sha256-4j3ly/rthCsJ7df2GLYrCm0f9THMbnwDkzhUbh5CbDI="; + }; + } +] diff --git a/overlays/zmotd/default.nix b/overlays/zmotd/default.nix new file mode 100644 index 0000000..fef3bb8 --- /dev/null +++ b/overlays/zmotd/default.nix @@ -0,0 +1,26 @@ +{ unstable }: +final: prev: { + zmotd = prev.stdenv.mkDerivation rec { + pname = "zmotd"; + version = "1.0"; + + src = prev.fetchFromGitea { + domain = "git.squi.bid"; + owner = "squibid"; + repo = "zmotd"; + rev = "48e7f34efb506f231bdc5070cb44a360fbd83cf1"; + sha256 = "sha256-8c5cMXzb6oq2kL6rZUrqjz/QeOryN7HXe+mYwjR9/k0="; + }; + deps = prev.callPackage ./build.zig.zon.nix {}; + nativeBuildInputs = [ unstable.zig.hook ]; # this hook is what defines the actual build process + zigBuildFlags = [ "--system" "${deps}" ]; # and this makes sure that zig doesn't try and fetch the dependencies on it's own + + meta = { + description = "Literally just a motd setter for my servers."; + license = "GPLv3"; + homepage = "https://git.squi.bid/squibid/zmotd"; + mainProgram = "zmotd"; + maintainers = [ "squibid" ]; + }; + }; +} diff --git a/secrets.yaml b/secrets.yaml new file mode 100644 index 0000000..1882a33 --- /dev/null +++ b/secrets.yaml @@ -0,0 +1,44 @@ +mail: + me: ENC[AES256_GCM,data:egjtukOJ0OQlwuk3nvwpeTaBQXfolZNlsqu9d1G5ryJJB6TvN9iydnJAG2Jn0v6rp3UTdX6dJNYUHQ9duLo7Y/Hn1LSyWEkA6g==,iv:/Xw6fow3kD2lX5wfHTgt9IGemG6kqXoQe0V5TjZK7mU=,tag:KSSN+sdI/CP7bvQF2S2kGQ==,type:str] +jellyfin: + zachary: ENC[AES256_GCM,data:GIDgfsxhU4fZVjP/cTmTvIA1aeP4lbd3Fz6tbPLdyL37KD+IKERgkxJmGwtt9GNwnJBsHE/xpH8ZAvloS1DykZZtEaqB0H6wuA==,iv:FM0d4tiQPzyoEiqEQF5YvNeClHXOhP+q+TaKGeyg/TE=,tag:v+sYDwQiCX7o+g7plcnQFg==,type:str] +users: + crown: ENC[AES256_GCM,data:6UAYcafxflvbsTXC1N3Ff0hAlWGjveYDUzbcXPSGfPX0uXg++bfjRwYo3JFgfJpJ/KN4MODPSxgjFAFnoZOnkyxk0UDSppDagQ==,iv:PWmxuj2caqRLASjftbl0tovNq2t1WoDoviJXs/OO8yI=,tag:EwJhROsHfj5cPkpxUCy+uw==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age14d55nfxlzm8t2yzplxpprygxmt99javafz9a8dh5llu87aww4qlswf6g0c + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwVjgxaW4ydXZDcWNHWG8y + Vm1JYjY4cG5HbDBUMzY4dFJYUzU5Wkk0dlhVCkcwVlRLaUl2OXNZbWYycVF4czRJ + QkJ4ZUUxN1VNbUErbnoyUnhTYlZmZ1kKLS0tIFNJMnZwdzBHRFIzcFNndDA2QU9R + dWdMcEdEYVZ1MURVN3RiUDZVZVRKd3cKgcINDvSO7cswTZSIFBUJMw49VTCXiw0+ + pNfExo2VAt+FiMTcErit7YG2Ti4jPBl4T2yPiS/LcEY0BZVq0t5i4A== + -----END AGE ENCRYPTED FILE----- + - recipient: age1rjtqzmywfr3zuzz0cn8eqnwp3x8ypzya9gcv6kvtplhudar5eayqq83ey4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtKzZrOUFBeFhKQU1RNDZP + RnZwdTFxL0E5NGtEc3prRitvOWptZFlSNW04Cll2eDNIa2tGZWd5cHdFWUJWeUx2 + ZHBoWk4ydENneVBMQlREQ1hSUjdjbHMKLS0tIGx1UWgweXNSbHpqM3RSQURUME90 + Y1Z1M1lQK0ErMFFpcWl1OElDV3FNRG8KzRfpQvGQbo+7W2IBJzJohF+X9s9OuIQn + e/pFYM0kNd4dBr/KKqXU5olt92b8H6QLGSuMx/rLNSYToFXjg7kPXw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1pnu4tkdxfcnefntdw262k4m8wuv3qe2894s4e6w5j8yshg8vlu6q9uq5tv + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZGszdTd2YjZsMkh2ckdZ + eGlRMUZzcDRDR3dGMlByL3FYUHh4NzJUNG1zCm9FTGczeitRRHdxWGltemM3MHFl + TjNhZ1Z5NDB2NHdCY1M2UVlXN2hFVFEKLS0tIEJGZTZ5RjBqNWtjRE5hVksyOCtj + c0N1WU40bFlRNGkvelR2Y2ZMY29lTDQKMjSDY5VP8Pcmz8FivXBPmuaZH7EaVaok + 2Z8+er/FQ+K7Y94BVcfPWCw16a2R30kqc32EFRyjGXgHCCOjJBv0Aw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-11-09T02:00:10Z" + mac: ENC[AES256_GCM,data:9Jg3aXMMe8Yhf3CycD+UPqlTg0E619dmOJENRe2sfwROdKxOXhiFqnuI4t262XW3IMpJdCbv3RIblklF6vPaqqJWkPqj4Jt2niF4Bq0oR+cRM+rAElYAZ6vviCWnjTjOhTD/UB2RYPFH77Ce7RQmR4c5H4D6uLaw1g3+9TLJPTE=,iv:p4mF2S1n+mTV+ny3hKbQ+tYqh+4HGURyUP9hiSdMZjs=,tag:dWCa87XTwH3mBHshUMxjiQ==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.1