diff --git a/home/toph/cloud/default.nix b/home/toph/cloud/default.nix new file mode 100644 index 0000000..fef0923 --- /dev/null +++ b/home/toph/cloud/default.nix @@ -0,0 +1,10 @@ +{ + pkgs, + ... +}: +{ + imports = [ + ## Required Configs ## + ../common/core # required + ]; +} diff --git a/hosts/common/optional/caddy/cloud.nix b/hosts/common/optional/caddy/cloud.nix deleted file mode 100644 index 810c7a3..0000000 --- a/hosts/common/optional/caddy/cloud.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - services.caddy.virtualHosts = { - "drive.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy http://localhost:8181 { - header_up Host {host} - # header_up X-Forwarded-For {remote} - # header_up X-Forwarded-Proto {scheme} - # header_up X-Forwarded-Protocol {scheme} - # header_up X-Forwarded-Port {server_port} - } - ''; - }; - }; -} diff --git a/hosts/common/optional/caddy/proxy.nix b/hosts/common/optional/caddy/proxy.nix deleted file mode 100644 index 5cf77b4..0000000 --- a/hosts/common/optional/caddy/proxy.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ - services.caddy.virtualHosts = { - "cloudflared.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy localhost:14333 - ''; - }; - }; -} diff --git a/hosts/common/optional/containers/komodo/default.nix b/hosts/common/optional/containers/komodo/default.nix index 76981b6..6bbca42 100644 --- a/hosts/common/optional/containers/komodo/default.nix +++ b/hosts/common/optional/containers/komodo/default.nix @@ -1,8 +1,8 @@ # Auto-generated using compose2nix v0.3.1. { - pkgs, + config, lib, - admin, + pkgs, ... }: let diff --git a/hosts/common/optional/system/pool.nix b/hosts/common/optional/system/pool.nix index a13b29b..c4d14e1 100644 --- a/hosts/common/optional/system/pool.nix +++ b/hosts/common/optional/system/pool.nix @@ -2,12 +2,8 @@ let username = config.hostSpec.username; homeDir = config.hostSpec.home; - pve-key = config.secretsSpec.ssh.privateKeys.pve; in { - # For less permission issues with SSHFS - programs.fuse.userAllowOther = true; - # Create the directories if they do not exist systemd.tmpfiles.rules = [ "d /pool 2775 ${username} ryot -" @@ -17,14 +13,16 @@ in # File system configuration fileSystems = { "/pool" = { - device = "${username}@cloud:/pool"; - fsType = "sshfs"; + device = "cloud:/"; + fsType = "nfs"; options = [ - "defaults" - "reconnect" "_netdev" - "allow_other" - "identityfile=${pve-key}" + "defaults" + "nfsvers=4.2" + "noacl" + "noatime" + "nofail" + "sec=sys" ]; }; @@ -37,4 +35,16 @@ in ]; }; }; + + # Ensure NFS client support is complete + boot.supportedFilesystems = [ "nfs" ]; + # services.rpcbind.enable = true; + + # Optional: Configure ID mapping if needed + services.nfs.idmapd.settings = { + General = { + Domain = "local"; # Must match on server and client + Verbosity = 0; + }; + }; } diff --git a/hosts/nixos/cloud/config/borg.nix b/hosts/nixos/cloud/config/borg.nix new file mode 100644 index 0000000..2e12aae --- /dev/null +++ b/hosts/nixos/cloud/config/borg.nix @@ -0,0 +1,140 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + # Borg backup destinations + dockerStorageRepo = "/pool/Backups/DockerStorage"; + forgejoRepo = "/pool/Backups/forgejo"; + + # Common borg backup settings + borgCommonSettings = '' + # Don't use cache to avoid issues with concurrent backups + export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes + + # Set this for non-interactive use + export BORG_NON_INTERACTIVE=yes + ''; + + # Initialize a repo if it doesn't exist + initRepo = repo: '' + if [ ! -d "${repo}" ]; then + mkdir -p "${repo}" + ${pkgs.borgbackup}/bin/borg init --encryption=none "${repo}" + fi + ''; + +in +{ + # Make sure borg is installed + environment.systemPackages = [ pkgs.borgbackup ]; + + # Docker Storage Backup Service + systemd.services.backup-docker-storage = { + description = "Backup Docker storage directory with Borg"; + + path = with pkgs; [ + borgbackup + coreutils + ]; + + script = '' + ${borgCommonSettings} + + # Initialize repository if needed + ${initRepo dockerStorageRepo} + + # Create backup + ${pkgs.borgbackup}/bin/borg create \ + --stats \ + --compression zstd,15 \ + --exclude '*.tmp' \ + --exclude '*/tmp/*' \ + ${dockerStorageRepo}::docker-{now:%Y-%m-%d_%H%M%S} \ + /mnt/drive1/DockerStorage + + # Prune old backups + ${pkgs.borgbackup}/bin/borg prune \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 3 \ + ${dockerStorageRepo} + ''; + + serviceConfig = { + Type = "oneshot"; + IOSchedulingClass = "idle"; + CPUSchedulingPolicy = "idle"; + Nice = 19; + }; + }; + + # Docker Storage Backup Timer (Weekly on Monday at 4am) + systemd.timers.backup-docker-storage = { + description = "Timer for Docker Storage Backup"; + + wantedBy = [ "timers.target" ]; + + timerConfig = { + OnCalendar = "Mon *-*-* 04:00:00"; + Persistent = true; # Run backup if system was off during scheduled time + RandomizedDelaySec = "5min"; # Add randomized delay + }; + }; + + # Forgejo Backup Service + systemd.services.backup-forgejo = { + description = "Backup Forgejo directory with Borg"; + + path = with pkgs; [ + borgbackup + coreutils + ]; + + script = '' + ${borgCommonSettings} + + # Initialize repository if needed + ${initRepo forgejoRepo} + + # Create backup + ${pkgs.borgbackup}/bin/borg create \ + --stats \ + --compression zstd,15 \ + --exclude '*.tmp' \ + --exclude '*/tmp/*' \ + ${forgejoRepo}::forgejo-{now:%Y-%m-%d_%H%M%S} \ + /pool/forgejo + + # Prune old backups + ${pkgs.borgbackup}/bin/borg prune \ + --keep-daily 14 \ + --keep-weekly 4 \ + --keep-monthly 3 \ + ${forgejoRepo} + ''; + + serviceConfig = { + Type = "oneshot"; + IOSchedulingClass = "idle"; + CPUSchedulingPolicy = "idle"; + Nice = 19; + }; + }; + + # Forgejo Backup Timer (Every 2 days at 4am) + systemd.timers.backup-forgejo = { + description = "Timer for Forgejo Backup"; + + wantedBy = [ "timers.target" ]; + + timerConfig = { + OnCalendar = "*-*-1/2 04:00:00"; # Every 2 days at 4am + Persistent = true; + RandomizedDelaySec = "5min"; + }; + }; +} diff --git a/hosts/nixos/cloud/config/caddy.nix b/hosts/nixos/cloud/config/caddy.nix new file mode 100644 index 0000000..88ee2da --- /dev/null +++ b/hosts/nixos/cloud/config/caddy.nix @@ -0,0 +1,19 @@ +{ + services.caddy = { + enable = true; + virtualHosts = { + "drive.ryot.foo" = { + useACMEHost = "ryot.foo"; + extraConfig = '' + reverse_proxy http://localhost:8282 { + header_up Host {host} + # header_up X-Forwarded-For {remote} + # header_up X-Forwarded-Proto {scheme} + # header_up X-Forwarded-Protocol {scheme} + # header_up X-Forwarded-Port {server_port} + } + ''; + }; + }; + }; +} diff --git a/hosts/nixos/cloud/config/default.nix b/hosts/nixos/cloud/config/default.nix new file mode 100644 index 0000000..b23287b --- /dev/null +++ b/hosts/nixos/cloud/config/default.nix @@ -0,0 +1,4 @@ +{ lib, ... }: +{ + imports = lib.custom.scanPaths ./.; +} diff --git a/hosts/nixos/cloud/config/filerun/compose.yml b/hosts/nixos/cloud/config/filerun/compose.yml new file mode 100644 index 0000000..97f617a --- /dev/null +++ b/hosts/nixos/cloud/config/filerun/compose.yml @@ -0,0 +1,38 @@ +name: filerun +services: + db: + image: mariadb:10.11 + user: 1001:1004 + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS + MYSQL_USER: ${DB_USER} + MYSQL_PASSWORD: ${DB_PASS} + MYSQL_DATABASE: ${DB_NAME} + restart: unless-stopped + volumes: + - /pool/filerun/db:/var/lib/mysql + + web: + image: filerun/filerun:8.1 + user: root + environment: + FR_DB_HOST: db + FR_DB_PORT: ${DB_PORT} + FR_DB_NAME: ${DB_NAME} + FR_DB_USER: ${DB_USER} + FR_DB_PASS: ${DB_PASS} + APACHE_RUN_USER: + APACHE_RUN_USER_ID: 1001 + APACHE_RUN_GROUP: + APACHE_RUN_GROUP_ID: 1004 + depends_on: + - db + links: + - db:db + ports: + - "8181:80" + restart: unless-stopped + volumes: + - /pool/filerun/html:/var/www/html + - /pool/filerun/user-files:/user-files + - /pool/:/pool diff --git a/hosts/nixos/cloud/config/filerun/default.nix b/hosts/nixos/cloud/config/filerun/default.nix new file mode 100644 index 0000000..f08f199 --- /dev/null +++ b/hosts/nixos/cloud/config/filerun/default.nix @@ -0,0 +1,119 @@ +# Auto-generated using compose2nix v0.3.1. +{ + config, + lib, + pkgs, + ... +}: +let + env = config.secretsSpec.docker.filerun; +in +{ + # Runtime + virtualisation.docker = { + enable = true; + autoPrune.enable = true; + }; + virtualisation.oci-containers.backend = "docker"; + + # Containers + virtualisation.oci-containers.containers."filerun-db" = { + image = "mariadb:10.11"; + environment = env; + volumes = [ + "/pool/filerun/db:/var/lib/mysql:rw" + ]; + user = "1001:1004"; + log-driver = "journald"; + extraOptions = [ + "--network-alias=db" + "--network=filerun_default" + ]; + }; + systemd.services."docker-filerun-db" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = [ + "docker-network-filerun_default.service" + ]; + requires = [ + "docker-network-filerun_default.service" + ]; + partOf = [ + "docker-compose-filerun-root.target" + ]; + wantedBy = [ + "docker-compose-filerun-root.target" + ]; + }; + virtualisation.oci-containers.containers."filerun-web" = { + image = "filerun/filerun:8.1"; + environment = env; + volumes = [ + "/pool/:/pool:rw" + "/pool/filerun/html:/var/www/html:rw" + "/pool/filerun/user-files:/user-files:rw" + ]; + ports = [ + "8282:80/tcp" + ]; + dependsOn = [ + "filerun-db" + ]; + user = "root"; + log-driver = "journald"; + extraOptions = [ + "--network-alias=web" + "--network=filerun_default" + ]; + }; + systemd.services."docker-filerun-web" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = [ + "docker-network-filerun_default.service" + ]; + requires = [ + "docker-network-filerun_default.service" + ]; + partOf = [ + "docker-compose-filerun-root.target" + ]; + wantedBy = [ + "docker-compose-filerun-root.target" + ]; + }; + + # Networks + systemd.services."docker-network-filerun_default" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "docker network rm -f filerun_default"; + }; + script = '' + docker network inspect filerun_default || docker network create filerun_default + ''; + partOf = [ "docker-compose-filerun-root.target" ]; + wantedBy = [ "docker-compose-filerun-root.target" ]; + }; + + # Root service + # When started, this will automatically create all resources and start + # the containers. When stopped, this will teardown all resources. + systemd.targets."docker-compose-filerun-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; + }; + wantedBy = [ "multi-user.target" ]; + }; +} diff --git a/hosts/nixos/cloud/config/nfs.nix b/hosts/nixos/cloud/config/nfs.nix new file mode 100644 index 0000000..2b4bc8c --- /dev/null +++ b/hosts/nixos/cloud/config/nfs.nix @@ -0,0 +1,23 @@ +{ config, lib, ... }: +{ + # Install and configure NFS server + services.nfs.server = { + enable = true; + + exports = '' + # Pool export - seen as root '/' by the client + /pool *(rw,insecure,no_subtree_check,no_root_squash,fsid=0,anonuid=1000,anongid=1004) + ''; + + extraNfsdConfig = "vers=4,4.1,4.2"; + }; + + # Ensure NFS client support is complete + # services.rpcbind.enable = true; + services.nfs.idmapd.settings = { + General = { + Domain = "local"; + Verbosity = 0; + }; + }; +} diff --git a/hosts/nixos/cloud/config/snapraid.nix b/hosts/nixos/cloud/config/snapraid.nix new file mode 100644 index 0000000..fa2b31e --- /dev/null +++ b/hosts/nixos/cloud/config/snapraid.nix @@ -0,0 +1,122 @@ +{ + pkgs, + inputs, + config, + ... +}: +let + + apprise-url = config.secretsSpec.api.apprise-url; + + snapraid-aio = inputs.snapraid-aio.nixosModules.default; + snapraid-aio-config = pkgs.writeTextFile { + name = "snapraid-aio.config"; + text = '' + CONFIG_VERSION="3.4" + CHECK_UPDATES=1 + + # Notification settings + APPRISE=0 + APPRISE_URL="" + APPRISE_ATTACH=1 + APPRISE_BIN="${pkgs.apprise}/bin/apprise" + APPRISE_EMAIL=1 + APPRISE_EMAIL_URL="${apprise-url}" + TELEGRAM=0 + DISCORD=0 + + # Thresholds for sync operations + DEL_THRESHOLD=500 + UP_THRESHOLD=500 + IGNORE_PATTERN="" + ADD_DEL_THRESHOLD=0 + SYNC_WARN_THRESHOLD=0 + + # Scrub settings + SCRUB_PERCENT=5 + SCRUB_AGE=10 + SCRUB_NEW=1 + SCRUB_DELAYED_RUN=0 + + # Performance and behavior settings + PREHASH=1 + FORCE_ZERO=0 + SPINDOWN=0 + VERBOSITY=1 + RETENTION_DAYS=30 + + # Logging settings + SNAPRAID_LOG_DIR="/var/log/snapraid" + SMART_LOG=1 + SMART_LOG_NOTIFY=0 + SNAP_STATUS=1 + SNAP_STATUS_NOTIFY=1 + + # Critical paths + SNAPRAID_CONF="/etc/snapraid.conf" + SNAPRAID_BIN="${pkgs.snapraid}/bin/snapraid" + + # Email settings (optional - uncomment and configure if needed) + # EMAIL_ADDRESS="your-email@example.com" + # FROM_EMAIL_ADDRESS="snapraid@your-server.com" + + # Advanced settings - typically no need to modify + CHK_FAIL=0 + DO_SYNC=1 + EMAIL_SUBJECT_PREFIX="(SnapRAID on $(hostname))" + SERVICES_STOPPED=0 + SYNC_WARN_FILE="/var/lib/snapraid-aio/snapRAID.warnCount" + SCRUB_COUNT_FILE="/var/lib/snapraid-aio/snapRAID.scrubCount" + TMP_OUTPUT="/var/lib/snapraid-aio/snapRAID.out" + SNAPRAID_LOG="/var/log/snapraid/snapraid.log" + ''; + }; + + snapraid-conf = pkgs.writeTextFile { + name = "snapraid.conf"; + text = '' + ## /etc/snapraid.conf ## + + # Defines the file to use as parity storage + parity /mnt/parity/snapraid.parity + + # Defines the files to use as content list + content /var/snapraid.content + content /mnt/drive1/snapraid.content + content /mnt/drive2/snapraid.content + content /mnt/drive3/snapraid.content + content /mnt/parity/snapraid.content + + # Defines the data disks to use + data d1 /mnt/drive1/ + data d2 /mnt/drive2/ + data d3 /mnt/drive3/ + + # Defines files and directories to exclude + exclude *.unrecoverable + exclude /tmp/ + exclude /lost+found/ + ''; + }; +in +{ + imports = [ + inputs.snapraid-aio.nixosModules.default + ]; + + # Make sure the SnapRAID config exists + environment.etc."snapraid.conf".source = snapraid-conf; + + # Create required directories + systemd.tmpfiles.rules = [ + "d /var/lib/snapraid-aio 0755 root root -" + "d /var/log/snapraid 0755 root root -" + ]; + + # Set up snapraid-aio service + services.snapraid-aio = { + enable = true; + configFile = snapraid-aio-config; + schedule = "*-*-* 03:00:00"; # Run daily at 3am + }; +} diff --git a/hosts/nixos/cloud/default.nix b/hosts/nixos/cloud/default.nix index a8bc76f..2028e92 100644 --- a/hosts/nixos/cloud/default.nix +++ b/hosts/nixos/cloud/default.nix @@ -1,12 +1,11 @@ ############################################################### # -# Prozy - LXC Container -# NixOS container, Ryzen 5 5600G (3 Cores), 2GB/2GB RAM/SWAP +# Cloud - LXC Container +# NixOS container, Ryzen 5 5600G (4th Cores), 4GB/4GB RAM/SWAP # ############################################################### { - inputs, lib, config, pkgs, @@ -19,6 +18,10 @@ let in { imports = lib.flatten [ + + ## Cloud Only ## + ./config + ## Hardware ## ./hardware.nix @@ -28,12 +31,10 @@ in ## Optional Configs ## "hosts/common/optional/acme" - "hosts/common/optional/caddy" "hosts/common/optional/docker.nix" - "hosts/common/optional/containers/cloudflared.nix" - ## Cloud Specific ## - "hosts/users/${username}" # # Not the best solution but I always have one user so ¯\_(ツ)_/¯ + ## Host user ## + "hosts/users/${username}" # Not the best solution but I always have one user so ¯\_(ツ)_/¯ ]) ]; @@ -50,21 +51,21 @@ in networking = { enableIPv6 = false; - firewall.allowedTCPPorts = firewall.allowedTCPPorts; + firewall = { + allowedTCPPorts = firewall.allowedTCPPorts; + allowedUDPPorts = firewall.allowedUDPPorts; + }; }; ## System-wide packages ## programs.nix-ld.enable = true; environment.systemPackages = with pkgs; [ + apprise lazydocker + mergerfs + snapraid ]; - environment.etc = { - "cloudflared/.keep" = { - text = "This directory is used to store cloudflared configuration files."; - }; - }; - # https://wiki.nixos.org/wiki/FAQ/When_do_I_update_stateVersion - system.stateVersion = "24.11"; + system.stateVersion = "25.05"; } diff --git a/hosts/nixos/cloud/hardware.nix b/hosts/nixos/cloud/hardware.nix index f0e401e..934e100 100644 --- a/hosts/nixos/cloud/hardware.nix +++ b/hosts/nixos/cloud/hardware.nix @@ -1,12 +1,45 @@ { lib, + config, ... }: +let + username = config.hostSpec.username; + homeDir = config.hostSpec.home; +in { imports = lib.flatten [ (map lib.custom.relativeToRoot [ "hosts/common/optional/system/lxc.nix" - "hosts/common/optional/system/pool.nix" ]) ]; + + # INFO: Cloud is the pool provider + fileSystems = { + "/pool" = { + fsType = "fuse.mergerfs"; + device = "/mnt/data*"; + options = [ + "cache.files=auto-full" + "defaults" + "allow_other" + "minfreespace=50G" + "fsname=mergerfs" + "category.create=mfs" + "nonempty" + "uid=1000" + "gid=1004" # Ryot group + "posix_acl=true" + ]; + }; + + "${homeDir}/git" = { + fsType = "none"; + device = "/pool/git"; + options = [ + "bind" + "nofail" + ]; + }; + }; } diff --git a/hosts/nixos/proxy/config/caddy.nix b/hosts/nixos/proxy/config/caddy.nix new file mode 100644 index 0000000..85fe253 --- /dev/null +++ b/hosts/nixos/proxy/config/caddy.nix @@ -0,0 +1,13 @@ +{ + services.caddy = { + enable = true; + virtualHosts = { + "cloudflared.ryot.foo" = { + useACMEHost = "ryot.foo"; + extraConfig = '' + reverse_proxy localhost:14333 + ''; + }; + }; + }; +} diff --git a/hosts/common/optional/containers/cloudflared.nix b/hosts/nixos/proxy/config/cloudflared.nix similarity index 100% rename from hosts/common/optional/containers/cloudflared.nix rename to hosts/nixos/proxy/config/cloudflared.nix diff --git a/hosts/nixos/proxy/config/default.nix b/hosts/nixos/proxy/config/default.nix new file mode 100644 index 0000000..b23287b --- /dev/null +++ b/hosts/nixos/proxy/config/default.nix @@ -0,0 +1,4 @@ +{ lib, ... }: +{ + imports = lib.custom.scanPaths ./.; +} diff --git a/hosts/nixos/proxy/default.nix b/hosts/nixos/proxy/default.nix index 42e831a..60722e9 100644 --- a/hosts/nixos/proxy/default.nix +++ b/hosts/nixos/proxy/default.nix @@ -1,6 +1,6 @@ ############################################################### # -# Prozy - LXC Container +# Proxy - LXC Container # NixOS container, Ryzen 5 5600G (3 Cores), 2GB/2GB RAM/SWAP # ############################################################### @@ -19,6 +19,9 @@ let in { imports = lib.flatten [ + ## Proxy Only ## + ./config + ## Hardware ## ./hardware.nix @@ -28,11 +31,9 @@ in ## Optional Configs ## "hosts/common/optional/acme" - "hosts/common/optional/caddy" "hosts/common/optional/docker.nix" - "hosts/common/optional/containers/cloudflared.nix" - ## Proxy Specific ## + ## Proxy User ## "hosts/users/${username}" # # Not the best solution but I always have one user so ¯\_(ツ)_/¯ ]) ]; diff --git a/secrets.nix b/secrets.nix index 9dbfd1b..e0b3329 100644 Binary files a/secrets.nix and b/secrets.nix differ