Compare commits
7 commits
3dd421cb08
...
10bad206e5
Author | SHA1 | Date | |
---|---|---|---|
10bad206e5 | |||
894cc4444c | |||
12ac221b46 | |||
4563b89b6e | |||
6d0942d351 | |||
80f932ba6d | |||
538d0a20a4 |
30 changed files with 877 additions and 1030 deletions
58
flake.lock
generated
58
flake.lock
generated
|
@ -69,46 +69,24 @@
|
|||
},
|
||||
"chaotic": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-schemas": "flake-schemas",
|
||||
"home-manager": "home-manager",
|
||||
"jovian": "jovian",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748304776,
|
||||
"narHash": "sha256-Eb+kBcm7ECpJ1HKjMgvZPo9TpGG0CpzfGRUc0FCZKP0=",
|
||||
"lastModified": 1750195929,
|
||||
"narHash": "sha256-5gaf/9wuxtfKqAFnNlX74Vz2VMURa/UzyfuEyYv4tZw=",
|
||||
"owner": "chaotic-cx",
|
||||
"repo": "nyx",
|
||||
"rev": "5278f55d2c2c568db38ed03370606b5e009e34df",
|
||||
"rev": "419a1cfaf34100008ff5fa97ce9ef3b194472f71",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "chaotic-cx",
|
||||
"ref": "nyxpkgs-unstable",
|
||||
"repo": "nyx",
|
||||
"rev": "5278f55d2c2c568db38ed03370606b5e009e34df",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"chaotic",
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1747392669,
|
||||
"narHash": "sha256-zky3+lndxKRu98PAwVK8kXPdg+Q1NVAhaI7YGrboKYA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "c3c27e603b0d9b5aac8a16236586696338856fbb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
|
@ -760,20 +738,24 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"chaotic",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1747323949,
|
||||
"narHash": "sha256-G4NwzhODScKnXqt2mEQtDFOnI0wU3L1WxsiHX3cID/0=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "f8e784353bde7cbf9a9046285c1caf41ac484ebe",
|
||||
"lastModified": 1750214276,
|
||||
"narHash": "sha256-1kniuhH70q4TAC/xIvjFYH46aHiLrbIlcr6fdrRwO1A=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "f9b2b2b1327ff6beab4662b8ea41689e0a57b8d4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
chaotic.url = "github:chaotic-cx/nyx/5278f55d2c2c568db38ed03370606b5e009e34df"; # Bleeding edge packages from Chaotic-AUR
|
||||
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; # Bleeding edge packages from Chaotic-AUR
|
||||
|
||||
## NixOS ##
|
||||
|
||||
|
|
|
@ -4,31 +4,141 @@
|
|||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
primaryMonitor = lib.custom.getPrimaryMonitor (config.monitors or [ ]);
|
||||
WIDTH = primaryMonitor.width or 1980;
|
||||
HEIGHT = primaryMonitor.height or 1080;
|
||||
REFRESH_RATE = primaryMonitor.refreshRate or 60;
|
||||
VRR = primaryMonitor.vrr or false;
|
||||
HDR = primaryMonitor.hdr or false;
|
||||
|
||||
# INFO: Example working commands for running games in steam-session
|
||||
## Rivals ##
|
||||
# SteamDeck=1 LD_PRELOAD="" PROTON_ENABLE_NVAPI=1 PROTON_ENABLE_WAYLAND=1 VKD3D_DISABLE_EXTENSIONS=VK_KHR_present_wait gamemoderun %command% -PSOCompileMode=1 -dx12
|
||||
## Stats Overlay ##
|
||||
# gamemoderun mangohud %command%
|
||||
## gamescope-run ##
|
||||
# GAMESCOPE_EXTRA_OPTS="--force-grab-cursor"; gamescope-run gamemoderun mangohud %command%
|
||||
|
||||
gamescope-base-script = pkgs.writeShellScript "gamescope-base" ''
|
||||
# Session configuration
|
||||
export CAP_SYS_NICE="eip"
|
||||
export DXVK_HDR="1"
|
||||
export ENABLE_GAMESCOPE_WSI="1"
|
||||
export ENABLE_HDR_WSI="1"
|
||||
export AMD_VULKAN_ICD=RADV
|
||||
export RADV_PERFTEST=aco
|
||||
export SDL_VIDEODRIVER="wayland"
|
||||
|
||||
# Steam environment variables for better compatibility
|
||||
export STEAM_FORCE_DESKTOPUI_SCALING=1
|
||||
export STEAM_GAMEPADUI=1
|
||||
export STEAM_GAMESCOPE_CLIENT=1
|
||||
|
||||
# Default resolution settings
|
||||
WIDTH=${toString WIDTH}
|
||||
HEIGHT=${toString HEIGHT}
|
||||
REFRESH_RATE=${toString REFRESH_RATE}
|
||||
|
||||
# Build gamescope options
|
||||
GAMESCOPE_OPTS="--fade-out-duration 200 -w $WIDTH -h $HEIGHT -r $REFRESH_RATE -f"
|
||||
GAMESCOPE_OPTS="$GAMESCOPE_OPTS --expose-wayland --backend sdl --rt --immediate-flips"
|
||||
${lib.optionalString HDR ''GAMESCOPE_OPTS="$GAMESCOPE_OPTS --hdr-enabled --hdr-debug-force-output --hdr-itm-enable"''}
|
||||
${lib.optionalString VRR ''GAMESCOPE_OPTS="$GAMESCOPE_OPTS --adaptive-sync"''}
|
||||
# Allow extra options to be passed
|
||||
GAMESCOPE_OPTS="$GAMESCOPE_OPTS $GAMESCOPE_EXTRA_OPTS"
|
||||
|
||||
# Execute gamescope with provided command
|
||||
exec ${lib.getExe pkgs.gamescope} $GAMESCOPE_OPTS -- "$@"
|
||||
'';
|
||||
|
||||
gamescope-run = pkgs.writeShellScriptBin "gamescope-run" ''
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: gamescope-run <command> [args...]"
|
||||
echo "Examples:"
|
||||
echo " gamescope-run heroic"
|
||||
echo " gamescope-run steam"
|
||||
echo " gamescope-run lutris"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec ${gamescope-base-script} "$@"
|
||||
'';
|
||||
|
||||
steam-session-script = pkgs.writeShellScript "steam-session-script" ''
|
||||
# Check launch mode parameter
|
||||
case "$1" in
|
||||
"desktop")
|
||||
# Launch Steam in desktop mode within gamescope
|
||||
exec ${gamescope-base-script} ${lib.getExe pkgs.steam}
|
||||
;;
|
||||
"bigpicture"|*)
|
||||
# Default: Launch Steam in Big Picture mode
|
||||
exec ${gamescope-base-script} ${lib.getExe pkgs.steam} -tenfoot -steamdeck -gamepadui
|
||||
;;
|
||||
esac
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = lib.custom.scanPaths ./.;
|
||||
|
||||
home.packages = with pkgs; [
|
||||
prismlauncher
|
||||
steam-run
|
||||
heroic
|
||||
gamescope-run
|
||||
# modrinth-app
|
||||
];
|
||||
|
||||
programs.mangohud = {
|
||||
enable = true;
|
||||
settings = {
|
||||
position = "top-right";
|
||||
cpu_stats = true;
|
||||
gpu_stats = true;
|
||||
fps = true;
|
||||
font_size = 12;
|
||||
cellpadding_y = -0.070;
|
||||
background_alpha = lib.mkForce 0.5;
|
||||
alpha = lib.mkForce 0.75;
|
||||
};
|
||||
};
|
||||
|
||||
xdg.desktopEntries = {
|
||||
steam = {
|
||||
name = "Steam Desktop (Gamescope)";
|
||||
comment = "Steam in Gamescope Session";
|
||||
exec = "${steam-session-script} desktop";
|
||||
icon = "steam";
|
||||
type = "Application";
|
||||
terminal = false;
|
||||
categories = [ "Game" ];
|
||||
actions = {
|
||||
desktop = {
|
||||
name = "Steam Big Picture (Gamescope)";
|
||||
exec = "${steam-session-script} bigpicture";
|
||||
};
|
||||
regular = {
|
||||
name = "Steam Desktop (No Gamescope)";
|
||||
exec = "${lib.getExe pkgs.steam}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
"com.heroicgameslauncher.hgl.desktop" = {
|
||||
name = "Heroic Games Launcher (Gamescope)";
|
||||
comment = "Heroic in Gamescope Session";
|
||||
exec = "${lib.getExe gamescope-run} heroic";
|
||||
icon = "com.heroicgameslauncher.hgl";
|
||||
type = "Application";
|
||||
terminal = false;
|
||||
categories = [ "Game" ];
|
||||
actions = {
|
||||
regular = {
|
||||
name = "Heroic (No Gamescope)";
|
||||
exec = "${lib.getExe pkgs.heroic}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# INFO: Example working commands for running gamescope, excuse the mess lol
|
||||
|
||||
# gamescope --adaptive-sync --backend sdl --expose-wayland --force-grab-cursor --framerate-limit 120 --immediate-flips --output-height 2160 --output-width 3840 --prefer-output DP-3 --rt -- gamemoderun %command%
|
||||
|
||||
# AMD_VULKAN_ICD=RADV RADV_PERFTEST=aco PROTON_USE_D9VK=1 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 120 --output-height 2160 --output-width 3840 --prefer-vk-device 7550:C0 --rt -F fsr -f --sharpness 4 -- gamemoderun %command%
|
||||
|
||||
# CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 144 --output-height 1440 --output-width 2560 --rt -F fsr -f --sharpness 4 -- gamemoderun %command%
|
||||
|
||||
# gamescope --expose-wayland --backend sdl --framerate-limit 144 --output-height 1440 --output-width 2560 --prefer-vk-device 7480:CF --prefer-output DP-2 --fullscreen --rt -F fsr --sharpness 4 -- gamemoderun %command%
|
||||
|
||||
# CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 160 --output-height 1440 --output-width 2560 --prefer-vk-device 73A5:C0 --prefer-output DP-2 --fullscreen --force-windows-fullscreen --rt -F fsr --sharpness 4 -- gamemoderun %command%
|
||||
|
||||
# SDL_VIDEODRIVER=x11 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 160 --output-height 1440 --output-width 2560 --force-windows-fullscreen -f --prefer-vk-device 73A5:C0 --prefer-output DP-2 -- gamemoderun %command% --use-d3d11
|
||||
|
||||
# SteamDeck=1 LD_PRELOAD="" PROTON_ENABLE_NVAPI=1 PROTON_ENABLE_WAYLAND=1 VKD3D_DISABLE_EXTENSIONS=VK_KHR_present_wait gamemoderun %command% -PSOCompileMode=1
|
||||
|
||||
#SDL_VIDEODRIVER=x11 AMD_VULKAN_ICD=RADV RADV_PERFTEST=aco PROTON_USE_D9VK=1 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 120 --output-height 2160 --output-width 3840 --prefer-vk-device 7550:C0 --rt -F fsr -f --sharpness 4 -- gamemoderun %command%
|
||||
|
||||
#SDL_VIDEODRIVER=x11 AMD_VULKAN_ICD=RADV RADV_PERFTEST=aco PROTON_USE_D9VK=1 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 120 --output-height 2160 --output-width 3840 --prefer-vk-device 7550:C0 --force-windows-fullscreen -f --prefer-vk-device 73A5:C0 --prefer-output DP-2 -- gamemoderun %command% --use-d3d11
|
||||
|
|
|
@ -325,6 +325,9 @@ with lib.hm.gvariant;
|
|||
''
|
||||
{"wm_class":"Code","spaceIndex":0}
|
||||
''
|
||||
''
|
||||
{"wm_class":"com.jaoushingan.WaydroidHelper","scratch_layer":true}
|
||||
''
|
||||
''
|
||||
{"wm_class":"com.mitchellh.ghostty","scratch_layer":true}
|
||||
''
|
||||
|
@ -337,12 +340,21 @@ with lib.hm.gvariant;
|
|||
''
|
||||
{"wm_class":"gnome-extensions-app","scratch_layer":true}
|
||||
''
|
||||
''
|
||||
{"wm_class":"org.gnome.Extensions","scratch_layer":true}
|
||||
''
|
||||
''
|
||||
{"wm_class":"org.gnome.Nautilus","scratch_layer":true}
|
||||
''
|
||||
''
|
||||
{"wm_class":"TelegramDesktop","spaceIndex":1}
|
||||
''
|
||||
''
|
||||
{"wm_class":"Waydroid","preferredWidth":"100%","spaceIndex":0,"title":""}
|
||||
''
|
||||
''
|
||||
{"wm_class":".gamescope-wrapped","preferredWidth":"100%","spaceIndex":2}
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
@ -6,15 +6,14 @@
|
|||
...
|
||||
}:
|
||||
{
|
||||
#TODO: Scripts might need a rework
|
||||
programs.fastfetch =
|
||||
let
|
||||
hostname = hostSpec.hostName;
|
||||
logoFile =
|
||||
let
|
||||
hostLogoPath = ./. + "/host/${hostname}.txt";
|
||||
hostLogoPath = ./. + "/host/images/${hostname}.png";
|
||||
in
|
||||
if builtins.pathExists hostLogoPath then hostLogoPath else ./host/nix.txt;
|
||||
if builtins.pathExists hostLogoPath then hostLogoPath else ./host/images/nix.png;
|
||||
weather = import ./scripts/weather.nix { inherit pkgs; };
|
||||
title = import ./scripts/title.nix { inherit pkgs; };
|
||||
in
|
||||
|
@ -22,12 +21,14 @@
|
|||
enable = true;
|
||||
settings = {
|
||||
logo = {
|
||||
source = builtins.readFile logoFile;
|
||||
type = "data";
|
||||
position = "left";
|
||||
type = "kitty";
|
||||
source = logoFile;
|
||||
width = 21; # columns
|
||||
height = 12; # rows
|
||||
padding = {
|
||||
top = 0;
|
||||
right = 0;
|
||||
top = 1;
|
||||
right = 2;
|
||||
left = 2;
|
||||
};
|
||||
};
|
||||
display = {
|
||||
|
@ -72,7 +73,11 @@
|
|||
type = "wm";
|
||||
}
|
||||
{
|
||||
text = "printf '%s%s' (string upper (string sub -l 1 $SHELL)) (string lower (string sub -s 2 $SHELL))";
|
||||
text =
|
||||
let
|
||||
name = lib.getName pkgs.fish;
|
||||
in
|
||||
"printf '%s%s' (string upper (string sub -l 1 ${name})) (string lower (string sub -s 2 ${name}))";
|
||||
key = "shell » {#keys}";
|
||||
keyColor = "1;33";
|
||||
type = "command";
|
||||
|
|
BIN
home/global/core/fastfetch/host/images/haze.png
Normal file
BIN
home/global/core/fastfetch/host/images/haze.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 123 KiB |
|
@ -21,7 +21,10 @@ for i in (seq 1 $num)
|
|||
set type2 $types[$j]
|
||||
set combination "$type1+$type2"
|
||||
echo "Creating with type $combination"
|
||||
nix run nixpkgs#chafa -- -s 24x11 -w 9 --symbols $combination --view-size 24x11 $input_png
|
||||
# chafa -s 23x12 -w 9 --stretch --symbols $combination --view-size 23x12 $input_png
|
||||
yay try chafa -- chafa -s 24x11 -w 9 --symbols $combination --view-size 24x11 $input_png
|
||||
# yay try chafa -- chafa -s 23x12 -w 9 --stretch --symbols $combination --view-size 23x12 $input_png
|
||||
end
|
||||
end
|
||||
|
||||
# yay try chafa -- chafa -s 23x12 -w 9 --symbols half --view-size 23x12 ./1989.png
|
||||
# fastfetch --logo-height 12 --logo-width 21 --logo-type kitty --logo-position left --logo-preserve-aspect-ratio --logo ./haze.png --logo-padding 2 --logo-padding-top 1
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
set fish_greeting # Disable greeting
|
||||
function fish_greeting
|
||||
if test "$SIXEL" = true; or string match -q xterm-256color "$TERM"
|
||||
fastfetch --logo-type sixel
|
||||
else
|
||||
fastfetch
|
||||
end
|
||||
end
|
||||
|
||||
## Aliases and Overrides ##
|
||||
|
||||
|
@ -108,7 +114,3 @@ function unzipz
|
|||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
## Fish Prompt ##
|
||||
|
||||
fastfetch
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# source = ./monitors.xml;
|
||||
# onChange = ''
|
||||
# cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||
# chmod 755 $HOME/.config/monitors.xml
|
||||
# '';
|
||||
# };
|
||||
}
|
||||
|
|
|
@ -30,10 +30,45 @@
|
|||
};
|
||||
};
|
||||
|
||||
monitors = [
|
||||
{
|
||||
name = "DP-2";
|
||||
primary = true;
|
||||
width = 3840;
|
||||
height = 2160;
|
||||
refreshRate = 60;
|
||||
x = 0;
|
||||
y = 0;
|
||||
scale = 1.0;
|
||||
transform = 0;
|
||||
enabled = true;
|
||||
hdr = true;
|
||||
vrr = true;
|
||||
}
|
||||
{
|
||||
name = "HDMI-1";
|
||||
primary = false;
|
||||
width = 1920;
|
||||
height = 1080;
|
||||
refreshRate = 60;
|
||||
x = 1920; # Positioned to the right of DP-1
|
||||
y = 0;
|
||||
scale = 1.0;
|
||||
transform = 1;
|
||||
enabled = true;
|
||||
}
|
||||
];
|
||||
|
||||
## TODO:
|
||||
## I want to automate this monitors.xml
|
||||
## But dk how to do it properly yet.
|
||||
## So for now ill still use this for the gnome config
|
||||
|
||||
home.file.".config/monitors_source" = {
|
||||
source = ./monitors.xml;
|
||||
onChange = ''
|
||||
cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||
chmod 755 $HOME/.config/monitors.xml
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,28 +1,6 @@
|
|||
<monitors version="2">
|
||||
<configuration>
|
||||
<layoutmode>physical</layoutmode>
|
||||
<logicalmonitor>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<scale>1</scale>
|
||||
<transform>
|
||||
<rotation>right</rotation>
|
||||
<flipped>no</flipped>
|
||||
</transform>
|
||||
<monitor>
|
||||
<monitorspec>
|
||||
<connector>HDMI-1-2</connector>
|
||||
<vendor>DEL</vendor>
|
||||
<product>DELL U2417H</product>
|
||||
<serial>XVNNT67I176L</serial>
|
||||
</monitorspec>
|
||||
<mode>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<rate>60.000</rate>
|
||||
</mode>
|
||||
</monitor>
|
||||
</logicalmonitor>
|
||||
<layoutmode>logical</layoutmode>
|
||||
<logicalmonitor>
|
||||
<x>1080</x>
|
||||
<y>0</y>
|
||||
|
@ -42,5 +20,27 @@
|
|||
</mode>
|
||||
</monitor>
|
||||
</logicalmonitor>
|
||||
<logicalmonitor>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<scale>1</scale>
|
||||
<transform>
|
||||
<rotation>right</rotation>
|
||||
<flipped>no</flipped>
|
||||
</transform>
|
||||
<monitor>
|
||||
<monitorspec>
|
||||
<connector>HDMI-2</connector>
|
||||
<vendor>DEL</vendor>
|
||||
<product>DELL U2417H</product>
|
||||
<serial>XVNNT67I176L</serial>
|
||||
</monitorspec>
|
||||
<mode>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<rate>60.000</rate>
|
||||
</mode>
|
||||
</monitor>
|
||||
</logicalmonitor>
|
||||
</configuration>
|
||||
</monitors>
|
||||
|
|
|
@ -2,22 +2,33 @@
|
|||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
hardware.opengl = {
|
||||
|
||||
hardware.graphics = {
|
||||
enable = true;
|
||||
driSupport32Bit = true;
|
||||
enable32Bit = true;
|
||||
};
|
||||
|
||||
# AMDgpu tool
|
||||
environment.systemPackages = with pkgs; [ lact ];
|
||||
systemd.packages = with pkgs; [ lact ];
|
||||
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
|
||||
environment.systemPackages = with pkgs; [
|
||||
lact
|
||||
gamescope
|
||||
# gamescope-wsi
|
||||
];
|
||||
|
||||
systemd = {
|
||||
packages = with pkgs; [ lact ];
|
||||
services.lactd.wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
programs = {
|
||||
steam = {
|
||||
enable = true;
|
||||
remotePlay.openFirewall = true;
|
||||
dedicatedServer.openFirewall = true;
|
||||
protontricks = {
|
||||
enable = true;
|
||||
package = pkgs.protontricks;
|
||||
|
@ -41,7 +52,7 @@
|
|||
|
||||
inherit (pkgs)
|
||||
gamemode
|
||||
gamescope # !!!: DO NOT ADD GAMESCOPE ANYWHERE ELSE IN CONFIG, IT WILL BREAK!
|
||||
mangohud
|
||||
gperftools
|
||||
keyutils
|
||||
libkrb5
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
{ pkgs, config, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
## DE ##
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
xkb = {
|
||||
layout = "us";
|
||||
variant = "";
|
||||
};
|
||||
};
|
||||
|
||||
services.desktopManager.gnome = {
|
||||
enable = true;
|
||||
extraGSettingsOverridePackages = [ pkgs.mutter ];
|
||||
|
@ -23,12 +20,25 @@
|
|||
enable = true;
|
||||
wayland = true;
|
||||
};
|
||||
|
||||
# Set the custom session as default
|
||||
defaultSession = lib.mkForce "gnome";
|
||||
|
||||
autoLogin = {
|
||||
enable = true;
|
||||
user = config.hostSpec.username;
|
||||
};
|
||||
};
|
||||
|
||||
# Configure keyboard layout for Wayland
|
||||
services.xserver = {
|
||||
enable = false;
|
||||
xkb = {
|
||||
layout = "us";
|
||||
variant = "";
|
||||
};
|
||||
};
|
||||
|
||||
#INFO: Fix for autoLogin
|
||||
systemd.services."getty@tty1".enable = false;
|
||||
systemd.services."autovt@tty1".enable = false;
|
||||
|
@ -53,7 +63,6 @@
|
|||
];
|
||||
|
||||
## Exclusions ##
|
||||
services.xserver.excludePackages = [ pkgs.xterm ];
|
||||
environment.gnome.excludePackages = (
|
||||
with pkgs;
|
||||
[
|
||||
|
|
44
hosts/global/common/waydroid.nix
Normal file
44
hosts/global/common/waydroid.nix
Normal file
|
@ -0,0 +1,44 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
# TODO: automate with monitors.nix when i fix that up again
|
||||
width = 1064;
|
||||
height = 1904;
|
||||
in
|
||||
{
|
||||
# Enable Waydroid
|
||||
virtualisation.waydroid.enable = true;
|
||||
|
||||
# Required packages
|
||||
environment.systemPackages = with pkgs; [
|
||||
waydroid
|
||||
waydroid-helper
|
||||
wl-clipboard # Clipboard sharing
|
||||
|
||||
# Waydroid setup script; run once to initialize
|
||||
(pkgs.writeShellScriptBin "waydroid-setup" ''
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
echo "Initializing WayDroid with GApps support..."
|
||||
sudo waydroid init -s GAPPS -f
|
||||
|
||||
echo "Setting default WayDroid properties..."
|
||||
sudo mkdir -p /var/lib/waydroid
|
||||
|
||||
# Create or update waydroid_base.prop
|
||||
echo "persist.waydroid.multi_windows=true" | sudo tee /var/lib/waydroid/waydroid_base.prop
|
||||
echo "persist.waydroid.width=${toString width}" | sudo tee -a /var/lib/waydroid/waydroid_base.prop
|
||||
echo "persist.waydroid.height=${toString height}" | sudo tee -a /var/lib/waydroid/waydroid_base.prop
|
||||
echo "sys.use_memfd=true" | sudo tee -a /var/lib/waydroid/waydroid_base.prop
|
||||
|
||||
echo "Setup complete!"
|
||||
echo ""
|
||||
echo "To start WayDroid:"
|
||||
echo " 1. sudo systemctl start waydroid-container"
|
||||
echo " 2. waydroid session start"
|
||||
echo " 3. waydroid show-full-ui"
|
||||
echo ""
|
||||
echo "To install APKs: waydroid app install /path/to/app.apk"
|
||||
'')
|
||||
];
|
||||
}
|
43
hosts/x86/cloud/config/backups.nix
Normal file
43
hosts/x86/cloud/config/backups.nix
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
services.backup = {
|
||||
enable = true;
|
||||
notificationUrl = lib.custom.mkAppriseUrl config.secretsSpec.users.admin.smtp "relay@ryot.foo";
|
||||
enableChainTimer = true;
|
||||
chainSchedule = "*-*-* 03:00:00";
|
||||
|
||||
# Maintenance jobs run first
|
||||
maintenanceJobs = [
|
||||
{
|
||||
name = "snapraid";
|
||||
title = "SnapRAID";
|
||||
service = "snapraid-aio.service";
|
||||
logPattern = "SnapRAID-*.out";
|
||||
logPath = "/var/log/snapraid";
|
||||
}
|
||||
];
|
||||
|
||||
# Backup jobs run after maintenance
|
||||
jobs = [
|
||||
{
|
||||
name = "forgejo";
|
||||
title = "Forgejo";
|
||||
repo = "/pool/Backups/forgejo";
|
||||
sourcePath = "/pool/forgejo";
|
||||
verbose = true;
|
||||
}
|
||||
{
|
||||
name = "docker-storage";
|
||||
title = "Docker Storage";
|
||||
repo = "/pool/Backups/DockerStorage";
|
||||
sourcePath = "/mnt/drive1/DockerStorage";
|
||||
verbose = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
# Common repositories
|
||||
dockerStorageRepo = "/pool/Backups/DockerStorage";
|
||||
forgejoRepo = "/pool/Backups/forgejo";
|
||||
|
||||
# Shared environment setup
|
||||
borgCommonSettings = ''
|
||||
# Don't use cache to avoid issues with concurrent backups
|
||||
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
||||
export BORG_NON_INTERACTIVE=yes
|
||||
'';
|
||||
|
||||
# Common packages needed for backups
|
||||
commonBorgPath = with pkgs; [
|
||||
borgbackup
|
||||
coreutils
|
||||
apprise
|
||||
gnugrep
|
||||
hostname
|
||||
util-linux
|
||||
gawk
|
||||
];
|
||||
|
||||
# Repository initialization
|
||||
initRepo = repo: ''
|
||||
if [ ! -d "${repo}" ]; then
|
||||
mkdir -p "${repo}"
|
||||
${pkgs.borgbackup}/bin/borg init --encryption=none "${repo}"
|
||||
fi
|
||||
'';
|
||||
|
||||
# Notification system
|
||||
apprise-url = config.secretsSpec.users.admin.smtp.notifyUrl;
|
||||
sendNotification = title: message: ''
|
||||
${pkgs.apprise}/bin/apprise -t "${title}" -b "${message}" "${apprise-url}" || true
|
||||
'';
|
||||
|
||||
# Statistics generation
|
||||
extractBorgStats = logFile: repoPath: ''
|
||||
{
|
||||
echo -e "\n==== BACKUP SUMMARY ====\n"
|
||||
grep -A10 "Archive name:" ${logFile} || echo "No archive stats found"
|
||||
echo -e "\n=== Compression ===\n"
|
||||
grep "Compressed size:" ${logFile} || echo "No compression stats found"
|
||||
echo -e "\n=== Duration ===\n"
|
||||
grep "Duration:" ${logFile} || echo "No duration stats found"
|
||||
grep "Throughput:" ${logFile} || echo "No throughput stats found"
|
||||
echo -e "\n=== Repository ===\n"
|
||||
${pkgs.borgbackup}/bin/borg info ${repoPath} --last 1 2>/dev/null || echo "Could not get repository info"
|
||||
echo -e "\n=== Storage Space ===\n"
|
||||
df -h ${repoPath} | grep -v "Filesystem" || echo "Could not get storage info"
|
||||
} > ${logFile}.stats
|
||||
STATS=$(cat ${logFile}.stats || echo "No stats available")
|
||||
'';
|
||||
|
||||
# Unified backup service generator with optional features
|
||||
mkBorgBackupService =
|
||||
{
|
||||
name,
|
||||
title,
|
||||
repo,
|
||||
sourcePath,
|
||||
keepDaily,
|
||||
keepWeekly,
|
||||
keepMonthly,
|
||||
schedule ? null,
|
||||
enableNotifications ? true,
|
||||
verbose ? false,
|
||||
}:
|
||||
let
|
||||
maybeCreateTimer = lib.optionalAttrs (schedule != null) {
|
||||
timers."backup-${name}" = {
|
||||
description = "Timer for ${title} Backup";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = schedule;
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "5min";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
logPrefix = if verbose then "set -x;" else "";
|
||||
in
|
||||
{
|
||||
services."backup-${name}" = {
|
||||
description = "Backup ${title} with Borg";
|
||||
inherit (commonServiceConfig) path serviceConfig;
|
||||
|
||||
script = ''
|
||||
${borgCommonSettings}
|
||||
${logPrefix} # Add verbose logging if enabled
|
||||
|
||||
LOG_FILE="/tmp/borg-${name}-backup-$(date +%Y%m%d-%H%M%S).log"
|
||||
${initRepo repo}
|
||||
|
||||
echo "Starting ${title} backup at $(date)" > $LOG_FILE
|
||||
ARCHIVE_NAME="${name}-$(date +%Y-%m-%d_%H%M%S)"
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# Add verbose output redirection if enabled
|
||||
${if verbose then "exec 3>&1 4>&2" else ""}
|
||||
${pkgs.borgbackup}/bin/borg create \
|
||||
--stats \
|
||||
--compression zstd,15 \
|
||||
--exclude '*.tmp' \
|
||||
--exclude '*/tmp/*' \
|
||||
${repo}::$ARCHIVE_NAME \
|
||||
${sourcePath} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
||||
|
||||
BACKUP_STATUS=$?
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
echo "Total time: $DURATION seconds ($(date -d@$DURATION -u +%H:%M:%S))" >> $LOG_FILE
|
||||
|
||||
${extractBorgStats "$LOG_FILE" "${repo}"}
|
||||
|
||||
echo -e "\nPruning old backups..." >> $LOG_FILE
|
||||
${pkgs.borgbackup}/bin/borg prune \
|
||||
--keep-daily ${toString keepDaily} \
|
||||
--keep-weekly ${toString keepWeekly} \
|
||||
--keep-monthly ${toString keepMonthly} \
|
||||
${repo} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
||||
|
||||
PRUNE_STATUS=$?
|
||||
|
||||
echo -e "\nRemaining archives after pruning:" >> $LOG_FILE
|
||||
${pkgs.borgbackup}/bin/borg list ${repo} >> $LOG_FILE 2>&1 || true
|
||||
|
||||
${
|
||||
if enableNotifications then
|
||||
''
|
||||
if [ $BACKUP_STATUS -eq 0 ] && [ $PRUNE_STATUS -eq 0 ]; then
|
||||
${sendNotification "✅ ${title} Backup Complete" "${title} backup completed successfully on $(hostname) at $(date)\nDuration: $(date -d@$DURATION -u +%H:%M:%S)\n\n$STATS"}
|
||||
else
|
||||
${sendNotification "❌ ${title} Backup Failed" "${title} backup failed on $(hostname) at $(date)\n\nBackup Status: $BACKUP_STATUS\nPrune Status: $PRUNE_STATUS\n\nPartial Stats:\n$STATS\n\nSee $LOG_FILE for details"}
|
||||
fi
|
||||
''
|
||||
else
|
||||
"echo 'Notifications disabled' >> $LOG_FILE"
|
||||
}
|
||||
|
||||
rm -f $LOG_FILE.stats
|
||||
exit $BACKUP_STATUS
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
// maybeCreateTimer;
|
||||
|
||||
# Common service configuration
|
||||
commonServiceConfig = {
|
||||
path = commonBorgPath;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
IOSchedulingClass = "idle";
|
||||
CPUSchedulingPolicy = "idle";
|
||||
Nice = 19;
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
borgbackup
|
||||
];
|
||||
|
||||
systemd = lib.mkMerge [
|
||||
(mkBorgBackupService {
|
||||
name = "docker-storage";
|
||||
title = "Docker Storage";
|
||||
repo = dockerStorageRepo;
|
||||
sourcePath = "/mnt/drive1/DockerStorage";
|
||||
# INFO: This shit confusing but basically
|
||||
# keeps the last 7 days,
|
||||
# then keeps AT LEAST ONE for last 4 weeks
|
||||
# and finally AT LEAST ONE for the last 3 months
|
||||
keepDaily = 7;
|
||||
keepWeekly = 4;
|
||||
keepMonthly = 3;
|
||||
# No schedule = no timer created
|
||||
# schedule = "*-*-* 03:00:00";
|
||||
enableNotifications = false;
|
||||
verbose = true;
|
||||
})
|
||||
|
||||
(mkBorgBackupService {
|
||||
name = "forgejo";
|
||||
title = "Forgejo";
|
||||
repo = forgejoRepo;
|
||||
sourcePath = "/pool/forgejo";
|
||||
keepDaily = 7;
|
||||
keepWeekly = 4;
|
||||
keepMonthly = 3;
|
||||
# schedule = "*-*-* 03:00:00";
|
||||
enableNotifications = false;
|
||||
verbose = true;
|
||||
})
|
||||
];
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
# Shared configuration
|
||||
logDir = "/var/log/backups";
|
||||
backupServices = [
|
||||
{
|
||||
name = "forgejo";
|
||||
title = "Forgejo";
|
||||
service = "backup-forgejo.service";
|
||||
logPattern = "borg-forgejo-backup-*.log";
|
||||
}
|
||||
{
|
||||
name = "docker_storage";
|
||||
title = "Docker Storage";
|
||||
service = "backup-docker-storage.service";
|
||||
logPattern = "borg-docker-storage-backup-*.log";
|
||||
}
|
||||
{
|
||||
name = "snapraid";
|
||||
title = "SnapRAID";
|
||||
service = "snapraid-aio.service";
|
||||
logPattern = "SnapRAID-*.out";
|
||||
logPath = "/var/log/snapraid";
|
||||
}
|
||||
];
|
||||
|
||||
# Helper functions
|
||||
users = config.secretsSpec.users;
|
||||
notify =
|
||||
title: message: logFile:
|
||||
let
|
||||
attachArg = if logFile == "" then "" else "--attach \"file://${logFile}\"";
|
||||
appriseUrl = lib.custom.mkAppriseUrl users.admin.smtp "relay@ryot.foo";
|
||||
in
|
||||
''
|
||||
${pkgs.apprise}/bin/apprise -vv -i "markdown" -t "${title}" \
|
||||
-b "${message}" \
|
||||
${attachArg} \
|
||||
"${appriseUrl}" || true
|
||||
'';
|
||||
|
||||
findLatestLog = pattern: path: ''
|
||||
find "${path}" -name "${pattern}" -type f -printf "%T@ %p\\n" 2>/dev/null \
|
||||
| sort -nr | head -1 | cut -d' ' -f2
|
||||
'';
|
||||
|
||||
# Generate safe variable name (replace hyphens with underscores)
|
||||
safeName = name: lib.replaceStrings [ "-" ] [ "_" ] name;
|
||||
|
||||
# Generate status variable references
|
||||
statusVarName = name: "STATUS_${safeName name}";
|
||||
|
||||
# Common script utilities
|
||||
scriptPrelude = ''
|
||||
set -uo pipefail
|
||||
LOG_FILE="${logDir}/backup-chain-$(date +%Y%m%d-%H%M%S).log"
|
||||
mkdir -p "${logDir}"
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
log() {
|
||||
echo "[$(date "+%Y-%m-%d %H:%M:%S")] $1"
|
||||
}
|
||||
|
||||
# Initialize all status variables
|
||||
${lib.concatMapStringsSep "\n" (s: "${statusVarName s.name}=1") backupServices}
|
||||
'';
|
||||
|
||||
# Service runner template
|
||||
runService =
|
||||
{
|
||||
name,
|
||||
title,
|
||||
service,
|
||||
logPattern,
|
||||
logPath ? "/tmp",
|
||||
}:
|
||||
''
|
||||
log "Starting ${title} maintenance..."
|
||||
systemctl start ${service} || true
|
||||
${statusVarName name}=$?
|
||||
log "${title} completed with status $${statusVarName name}"
|
||||
|
||||
SERVICE_LOG=$(${findLatestLog logPattern logPath})
|
||||
if [ -n "$SERVICE_LOG" ]; then
|
||||
log "Appending ${title} log: $SERVICE_LOG"
|
||||
echo -e "\n\n===== ${title} LOG ($(basename "$SERVICE_LOG")) =====\n" >> "$LOG_FILE"
|
||||
cat "$SERVICE_LOG" >> "$LOG_FILE"
|
||||
|
||||
# Add SnapRAID-specific summary
|
||||
if [ "${name}" = "snapraid" ]; then
|
||||
echo -e "\n=== SnapRAID Summary ===" >> "$LOG_FILE"
|
||||
grep -E '(Scrub|Sync|Diff|smart)' "$SERVICE_LOG" | tail -n 10 >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
||||
# Build the service execution script
|
||||
serviceExecution = lib.concatMapStrings runService backupServices;
|
||||
|
||||
# Generate status summary lines
|
||||
statusSummaryLines = lib.concatMapStringsSep "\n" (
|
||||
s:
|
||||
let
|
||||
varName = statusVarName s.name;
|
||||
in
|
||||
"- **${s.title}:** \$([ \$${varName} -eq 0 ] && echo '✅ Success' || echo '❌ Failed') (Exit: \$${varName})"
|
||||
) backupServices;
|
||||
|
||||
# Notification logic with cleaner formatting
|
||||
notificationLogic =
|
||||
let
|
||||
statusVars = map (s: statusVarName s.name) backupServices;
|
||||
statusChecks = lib.concatMapStringsSep "\n" (var: "[ \$${var} -eq 0 ] && ") statusVars;
|
||||
in
|
||||
''
|
||||
# Calculate overall status
|
||||
OVERALL_STATUS=0
|
||||
${lib.concatMapStringsSep "\n" (var: "if [ \$${var} -ne 0 ]; then OVERALL_STATUS=1; fi") statusVars}
|
||||
|
||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
HOSTNAME=$(hostname)
|
||||
|
||||
SUMMARY=$(cat << EOF
|
||||
# Backup Chain Complete
|
||||
|
||||
**Host:** $HOSTNAME
|
||||
**Timestamp:** $TIMESTAMP
|
||||
**Overall Status:** $([ $OVERALL_STATUS -eq 0 ] && echo '✅ Success' || echo '⚠️ Failure')
|
||||
|
||||
## Service Status:
|
||||
${statusSummaryLines}
|
||||
|
||||
**Log Path:** $LOG_FILE
|
||||
EOF)
|
||||
|
||||
if [ $OVERALL_STATUS -eq 0 ]; then
|
||||
${notify "✅ Backup Success" "$SUMMARY" "$LOG_FILE"}
|
||||
else
|
||||
${notify "⚠️ Backup Issues" "$SUMMARY" "$LOG_FILE"}
|
||||
fi
|
||||
|
||||
exit $OVERALL_STATUS
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
imports = lib.custom.scanPaths ./.;
|
||||
|
||||
systemd.services.backup-chain = {
|
||||
description = "Orchestrated Backup Chain";
|
||||
path = with pkgs; [
|
||||
apprise
|
||||
coreutils
|
||||
findutils
|
||||
gawk
|
||||
gnugrep
|
||||
hostname
|
||||
systemd
|
||||
util-linux
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
Nice = 19;
|
||||
IOSchedulingClass = "idle";
|
||||
CPUSchedulingPolicy = "idle";
|
||||
};
|
||||
|
||||
script = ''
|
||||
${scriptPrelude}
|
||||
log "Initializing backup chain on $(hostname)"
|
||||
|
||||
${serviceExecution}
|
||||
|
||||
log "Finalizing backup chain"
|
||||
${notificationLogic}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.backup-chain = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 03:00:00";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "5min";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.apprise ];
|
||||
systemd.tmpfiles.rules = [ "d ${logDir} 0755 root root -" ];
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
pkgs,
|
||||
inputs,
|
||||
config,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
imports = lib.flatten [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
## Boot ##
|
||||
boot = {
|
||||
loader = {
|
||||
systemd-boot = {
|
||||
enable = true;
|
||||
# When using plymouth, initrd can expand by a lot each time, so limit how many we keep around
|
||||
configurationLimit = lib.mkDefault 10;
|
||||
};
|
||||
efi.canTouchEfiVariables = true;
|
||||
timeout = 3;
|
||||
};
|
||||
|
||||
# Use the cachyos kernel for better performance
|
||||
kernelPackages = pkgs.linuxPackages_cachyos;
|
||||
|
||||
initrd = {
|
||||
systemd.enable = true;
|
||||
verbose = false;
|
||||
availableKernelModules = [
|
||||
"nvme"
|
||||
"xhci_pci"
|
||||
"ahci"
|
||||
"usb_storage"
|
||||
"usbhid"
|
||||
"sd_mod"
|
||||
];
|
||||
kernelModules = [ ];
|
||||
};
|
||||
kernelModules = [
|
||||
"kvm-amd"
|
||||
"amdgpu"
|
||||
];
|
||||
extraModulePackages = [ ];
|
||||
};
|
||||
|
||||
# For less permission issues with SSHFS
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
# FIXME: Fix on first boot
|
||||
# fileSystems = {
|
||||
# "/" = {
|
||||
# device = "/dev/disk/by-uuid/d38c182c-6f05-4bf3-8a45-5532c10fd342";
|
||||
# fsType = "ext4";
|
||||
# };
|
||||
|
||||
# "/boot" = {
|
||||
# device = "/dev/disk/by-uuid/5B39-A7CB";
|
||||
# fsType = "vfat";
|
||||
# options = [
|
||||
# "fmask=0077"
|
||||
# "dmask=0077"
|
||||
# ];
|
||||
# };
|
||||
# };
|
||||
|
||||
# FIXME: Fix on first boot
|
||||
# swapDevices = [ { device = "/dev/disk/by-uuid/6586847d-eba9-4317-9077-98ae9b2812c9"; } ];
|
||||
|
||||
time.hardwareClockInLocalTime = true; # Fixes windows dual-boot time issues
|
||||
|
||||
# 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.enp5s0.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableAllFirmware;
|
||||
|
||||
}
|
||||
|
||||
# STUFF ABOUT CHAOTIC NIX CACHE
|
||||
# nix eval 'github:chaotic-cx/nyx/nyxpkgs-unstable#linuxPackages_cachyos.kernel.outPath'
|
||||
# nix eval 'chaotic#linuxPackages_cachyos.kernel.outPath'
|
||||
# nix eval '/pool/git/Nix/dot.nix#nixosConfigurations.rune.config.boot.kernelPackages.kernel.outPath'
|
||||
# curl -L 'https://chaotic-nyx.cachix.org/{{HASH}}.narinfo'
|
||||
# sudo nixos-rebuild switch --flake ./git/Nix/dot.nix/. --option 'extra-substituters' 'https://chaotic-nyx.cachix.org/' --option extra-trusted-public-keys "chaotic-nyx.cachix.org-1:HfnXSw4pj95iI/n17rIDy40agHj12WfF+Gqk6SonIT8="
|
|
@ -1,64 +1,90 @@
|
|||
# FIXME: FIX to hardware.fix once out of VM, this is TEMP vm hardware config
|
||||
{
|
||||
pkgs,
|
||||
inputs,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
let
|
||||
username = config.hostSpec.username;
|
||||
in
|
||||
|
||||
{
|
||||
imports = lib.flatten [
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
## Boot ##
|
||||
boot = {
|
||||
loader = {
|
||||
grub = {
|
||||
systemd-boot = {
|
||||
enable = true;
|
||||
device = "/dev/vda";
|
||||
useOSProber = true;
|
||||
# When using plymouth, initrd can expand by a lot each time, so limit how many we keep around
|
||||
configurationLimit = lib.mkDefault 10;
|
||||
};
|
||||
efi.canTouchEfiVariables = true;
|
||||
timeout = 3;
|
||||
};
|
||||
|
||||
# use latest kernel
|
||||
kernelPackages = pkgs.linuxPackages_latest;
|
||||
# Use the cachyos kernel for better performance
|
||||
kernelPackages = pkgs.linuxPackages_cachyos;
|
||||
|
||||
initrd = {
|
||||
availableKernelModules = [
|
||||
"ahci"
|
||||
"xhci_pci"
|
||||
"virtio_pci"
|
||||
"sr_mod"
|
||||
"virtio_blk"
|
||||
];
|
||||
systemd.enable = true;
|
||||
verbose = false;
|
||||
availableKernelModules = [
|
||||
"nvme"
|
||||
"xhci_pci"
|
||||
"ahci"
|
||||
"usb_storage"
|
||||
"usbhid"
|
||||
"sd_mod"
|
||||
];
|
||||
kernelModules = [ ];
|
||||
};
|
||||
kernelModules = [ "kvm-amd" ];
|
||||
kernelModules = [
|
||||
"kvm-amd"
|
||||
"amdgpu"
|
||||
];
|
||||
extraModulePackages = [ ];
|
||||
};
|
||||
|
||||
# For less permission issues with SSHFS
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-uuid/5f1ad3a9-18ce-42ab-83ea-b67bccaa6972";
|
||||
device = "/dev/disk/by-uuid/a19362ba-329b-4688-be05-c180f4212d97";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
swapDevices = [
|
||||
{ device = "/dev/disk/by-uuid/e3fc8d25-31a5-48c1-8c81-c6c237f671bb"; }
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-uuid/8E6A-D6AE";
|
||||
fsType = "vfat";
|
||||
options = [
|
||||
"fmask=0077"
|
||||
"dmask=0077"
|
||||
];
|
||||
};
|
||||
|
||||
swapDevices = [
|
||||
{ device = "/dev/disk/by-uuid/2c3c3142-93c9-4be7-b9c1-5e9bc45d9fff"; }
|
||||
];
|
||||
|
||||
time.hardwareClockInLocalTime = true; # Fixes windows dual-boot time issues
|
||||
|
||||
# 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.enp1s0.useDHCP = lib.mkDefault true;
|
||||
# networking.interfaces.enp5s0.useDHCP = lib.mkDefault true;
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableAllFirmware;
|
||||
|
||||
}
|
||||
|
||||
# STUFF ABOUT CHAOTIC NIX CACHE
|
||||
# nix eval 'github:chaotic-cx/nyx/nyxpkgs-unstable#linuxPackages_cachyos.kernel.outPath'
|
||||
# nix eval 'chaotic#linuxPackages_cachyos.kernel.outPath'
|
||||
# nix eval '/pool/git/Nix/dot.nix#nixosConfigurations.rune.config.boot.kernelPackages.kernel.outPath'
|
||||
# curl -L 'https://chaotic-nyx.cachix.org/{{HASH}}.narinfo'
|
||||
# sudo nixos-rebuild switch --flake ./git/Nix/dot.nix/. --option 'extra-substituters' 'https://chaotic-nyx.cachix.org/' --option extra-trusted-public-keys "chaotic-nyx.cachix.org-1:HfnXSw4pj95iI/n17rIDy40agHj12WfF+Gqk6SonIT8="
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
###############################################################
|
||||
#
|
||||
# Haze - Cesar's Desktop
|
||||
# NixOS running on Ryzen 7 ___ , Radeon RX 6950 XT, 32GB RAM
|
||||
# NixOS running on Ryzen 5 7600x, Radeon RX 7600, 32GB RAM
|
||||
#
|
||||
###############################################################
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ in
|
|||
"hosts/global/common/nvtop.nix" # GPU monitor (not available in home-manager)
|
||||
"hosts/global/common/plymouth.nix" # fancy boot screen
|
||||
"hosts/global/common/solaar.nix" # Logitech Unifying Receiver support
|
||||
"hosts/global/common/waydroid.nix" # Android container
|
||||
"hosts/global/common/vial.nix" # KB setup
|
||||
])
|
||||
];
|
||||
|
|
23
hosts/x86/sock/config/backups.nix
Normal file
23
hosts/x86/sock/config/backups.nix
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
services.backup = {
|
||||
enable = true;
|
||||
notificationUrl = lib.custom.mkAppriseUrl config.secretsSpec.users.admin.smtp "relay@ryot.foo";
|
||||
enableChainTimer = true;
|
||||
|
||||
jobs = [
|
||||
{
|
||||
name = "ochre-storage";
|
||||
title = "Ochre Storage";
|
||||
repo = "/pool/Backups/OchreStorage";
|
||||
sourcePath = "/OchreStorage";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
# Common repositories
|
||||
ochreStorageRepo = "/pool/Backups/OchreStorage";
|
||||
|
||||
# Shared environment setup
|
||||
borgCommonSettings = ''
|
||||
# Don't use cache to avoid issues with concurrent backups
|
||||
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
||||
export BORG_NON_INTERACTIVE=yes
|
||||
'';
|
||||
|
||||
# Common packages needed for backups
|
||||
commonBorgPath = with pkgs; [
|
||||
borgbackup
|
||||
coreutils
|
||||
apprise
|
||||
gnugrep
|
||||
hostname
|
||||
util-linux
|
||||
gawk
|
||||
];
|
||||
|
||||
# Repository initialization
|
||||
initRepo = repo: ''
|
||||
if [ ! -d "${repo}" ]; then
|
||||
mkdir -p "${repo}"
|
||||
${pkgs.borgbackup}/bin/borg init --encryption=none "${repo}"
|
||||
fi
|
||||
'';
|
||||
|
||||
# Notification system
|
||||
apprise-url = config.secretsSpec.users.admin.smtp.notifyUrl;
|
||||
sendNotification = title: message: ''
|
||||
${pkgs.apprise}/bin/apprise -t "${title}" -b "${message}" "${apprise-url}" || true
|
||||
'';
|
||||
|
||||
# Statistics generation
|
||||
extractBorgStats = logFile: repoPath: ''
|
||||
{
|
||||
echo -e "\n==== BACKUP SUMMARY ====\n"
|
||||
grep -A10 "Archive name:" ${logFile} || echo "No archive stats found"
|
||||
echo -e "\n=== Compression ===\n"
|
||||
grep "Compressed size:" ${logFile} || echo "No compression stats found"
|
||||
echo -e "\n=== Duration ===\n"
|
||||
grep "Duration:" ${logFile} || echo "No duration stats found"
|
||||
grep "Throughput:" ${logFile} || echo "No throughput stats found"
|
||||
echo -e "\n=== Repository ===\n"
|
||||
${pkgs.borgbackup}/bin/borg info ${repoPath} --last 1 2>/dev/null || echo "Could not get repository info"
|
||||
echo -e "\n=== Storage Space ===\n"
|
||||
df -h ${repoPath} | grep -v "Filesystem" || echo "Could not get storage info"
|
||||
} > ${logFile}.stats
|
||||
STATS=$(cat ${logFile}.stats || echo "No stats available")
|
||||
'';
|
||||
|
||||
# Unified backup service generator with optional features
|
||||
mkBorgBackupService =
|
||||
{
|
||||
name,
|
||||
title,
|
||||
repo,
|
||||
sourcePath,
|
||||
keepDaily,
|
||||
keepWeekly,
|
||||
keepMonthly,
|
||||
schedule ? null,
|
||||
enableNotifications ? true,
|
||||
verbose ? false,
|
||||
}:
|
||||
let
|
||||
maybeCreateTimer = lib.optionalAttrs (schedule != null) {
|
||||
timers."backup-${name}" = {
|
||||
description = "Timer for ${title} Backup";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = schedule;
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "5min";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
logPrefix = if verbose then "set -x;" else "";
|
||||
in
|
||||
{
|
||||
services."backup-${name}" = {
|
||||
description = "Backup ${title} with Borg";
|
||||
inherit (commonServiceConfig) path serviceConfig;
|
||||
|
||||
script = ''
|
||||
${borgCommonSettings}
|
||||
${logPrefix} # Add verbose logging if enabled
|
||||
|
||||
LOG_FILE="/tmp/borg-${name}-backup-$(date +%Y%m%d-%H%M%S).log"
|
||||
${initRepo repo}
|
||||
|
||||
echo "Starting ${title} backup at $(date)" > $LOG_FILE
|
||||
ARCHIVE_NAME="${name}-$(date +%Y-%m-%d_%H%M%S)"
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# Add verbose output redirection if enabled
|
||||
${if verbose then "exec 3>&1 4>&2" else ""}
|
||||
${pkgs.borgbackup}/bin/borg create \
|
||||
--stats \
|
||||
--compression zstd,15 \
|
||||
--exclude '*.tmp' \
|
||||
--exclude '*/tmp/*' \
|
||||
${repo}::$ARCHIVE_NAME \
|
||||
${sourcePath} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
||||
|
||||
BACKUP_STATUS=$?
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
echo "Total time: $DURATION seconds ($(date -d@$DURATION -u +%H:%M:%S))" >> $LOG_FILE
|
||||
|
||||
${extractBorgStats "$LOG_FILE" "${repo}"}
|
||||
|
||||
echo -e "\nPruning old backups..." >> $LOG_FILE
|
||||
${pkgs.borgbackup}/bin/borg prune \
|
||||
--keep-daily ${toString keepDaily} \
|
||||
--keep-weekly ${toString keepWeekly} \
|
||||
--keep-monthly ${toString keepMonthly} \
|
||||
${repo} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
||||
|
||||
PRUNE_STATUS=$?
|
||||
|
||||
echo -e "\nRemaining archives after pruning:" >> $LOG_FILE
|
||||
${pkgs.borgbackup}/bin/borg list ${repo} >> $LOG_FILE 2>&1 || true
|
||||
|
||||
${
|
||||
if enableNotifications then
|
||||
''
|
||||
if [ $BACKUP_STATUS -eq 0 ] && [ $PRUNE_STATUS -eq 0 ]; then
|
||||
${sendNotification "✅ ${title} Backup Complete" "${title} backup completed successfully on $(hostname) at $(date)\nDuration: $(date -d@$DURATION -u +%H:%M:%S)\n\n$STATS"}
|
||||
else
|
||||
${sendNotification "❌ ${title} Backup Failed" "${title} backup failed on $(hostname) at $(date)\n\nBackup Status: $BACKUP_STATUS\nPrune Status: $PRUNE_STATUS\n\nPartial Stats:\n$STATS\n\nSee $LOG_FILE for details"}
|
||||
fi
|
||||
''
|
||||
else
|
||||
"echo 'Notifications disabled' >> $LOG_FILE"
|
||||
}
|
||||
|
||||
rm -f $LOG_FILE.stats
|
||||
exit $BACKUP_STATUS
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
// maybeCreateTimer;
|
||||
|
||||
# Common service configuration
|
||||
commonServiceConfig = {
|
||||
path = commonBorgPath;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
IOSchedulingClass = "idle";
|
||||
CPUSchedulingPolicy = "idle";
|
||||
Nice = 19;
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
borgbackup
|
||||
];
|
||||
|
||||
systemd = lib.mkMerge [
|
||||
(mkBorgBackupService {
|
||||
name = "ochre-storage";
|
||||
title = "Ochre Storage";
|
||||
repo = ochreStorageRepo;
|
||||
sourcePath = "/OchreStorage";
|
||||
# INFO: This shit confusing but basically
|
||||
# keeps the last 7 days,
|
||||
# then keeps AT LEAST ONE for last 4 weeks
|
||||
# and finally AT LEAST ONE for the last 3 months
|
||||
keepDaily = 7;
|
||||
keepWeekly = 4;
|
||||
keepMonthly = 3;
|
||||
# No schedule = no timer created
|
||||
# schedule = "*-*-* 03:00:00";
|
||||
enableNotifications = false;
|
||||
verbose = true;
|
||||
})
|
||||
];
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
# Shared configuration
|
||||
logDir = "/var/log/backups";
|
||||
backupServices = [
|
||||
{
|
||||
name = "ochre_storage";
|
||||
title = "Ochre Storage";
|
||||
service = "backup-ochre-storage.service";
|
||||
logPattern = "borg-ochre-storage-backup-*.log";
|
||||
}
|
||||
];
|
||||
|
||||
# Helper functions
|
||||
users = config.secretsSpec.users;
|
||||
notify =
|
||||
title: message: logFile:
|
||||
let
|
||||
attachArg = if logFile == "" then "" else "--attach \"file://${logFile}\"";
|
||||
appriseUrl = lib.custom.mkAppriseUrl users.admin.smtp "relay@ryot.foo";
|
||||
in
|
||||
''
|
||||
${pkgs.apprise}/bin/apprise -vv -i "markdown" -t "${title}" \
|
||||
-b "${message}" \
|
||||
${attachArg} \
|
||||
"${appriseUrl}" || true
|
||||
'';
|
||||
|
||||
findLatestLog = pattern: path: ''
|
||||
find "${path}" -name "${pattern}" -type f -printf "%T@ %p\\n" 2>/dev/null \
|
||||
| sort -nr | head -1 | cut -d' ' -f2
|
||||
'';
|
||||
|
||||
# Generate safe variable name (replace hyphens with underscores)
|
||||
safeName = name: lib.replaceStrings [ "-" ] [ "_" ] name;
|
||||
|
||||
# Generate status variable references
|
||||
statusVarName = name: "STATUS_${safeName name}";
|
||||
|
||||
# Common script utilities
|
||||
scriptPrelude = ''
|
||||
set -uo pipefail
|
||||
LOG_FILE="${logDir}/backup-chain-$(date +%Y%m%d-%H%M%S).log"
|
||||
mkdir -p "${logDir}"
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
log() {
|
||||
echo "[$(date "+%Y-%m-%d %H:%M:%S")] $1"
|
||||
}
|
||||
|
||||
# Initialize all status variables
|
||||
${lib.concatMapStringsSep "\n" (s: "${statusVarName s.name}=1") backupServices}
|
||||
'';
|
||||
|
||||
# Service runner template
|
||||
runService =
|
||||
{
|
||||
name,
|
||||
title,
|
||||
service,
|
||||
logPattern,
|
||||
logPath ? "/tmp",
|
||||
}:
|
||||
''
|
||||
log "Starting ${title} maintenance..."
|
||||
systemctl start ${service} || true
|
||||
${statusVarName name}=$?
|
||||
log "${title} completed with status $${statusVarName name}"
|
||||
|
||||
SERVICE_LOG=$(${findLatestLog logPattern logPath})
|
||||
if [ -n "$SERVICE_LOG" ]; then
|
||||
log "Appending ${title} log: $SERVICE_LOG"
|
||||
echo -e "\n\n===== ${title} LOG ($(basename "$SERVICE_LOG")) =====\n" >> "$LOG_FILE"
|
||||
cat "$SERVICE_LOG" >> "$LOG_FILE"
|
||||
|
||||
# Add SnapRAID-specific summary
|
||||
if [ "${name}" = "snapraid" ]; then
|
||||
echo -e "\n=== SnapRAID Summary ===" >> "$LOG_FILE"
|
||||
grep -E '(Scrub|Sync|Diff|smart)' "$SERVICE_LOG" | tail -n 10 >> "$LOG_FILE"
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
||||
# Build the service execution script
|
||||
serviceExecution = lib.concatMapStrings runService backupServices;
|
||||
|
||||
# Generate status summary lines
|
||||
statusSummaryLines = lib.concatMapStringsSep "\n" (
|
||||
s:
|
||||
let
|
||||
varName = statusVarName s.name;
|
||||
in
|
||||
"- **${s.title}:** \$([ \$${varName} -eq 0 ] && echo '✅ Success' || echo '❌ Failed') (Exit: \$${varName})"
|
||||
) backupServices;
|
||||
|
||||
# Notification logic with cleaner formatting
|
||||
notificationLogic =
|
||||
let
|
||||
statusVars = map (s: statusVarName s.name) backupServices;
|
||||
statusChecks = lib.concatMapStringsSep "\n" (var: "[ \$${var} -eq 0 ] && ") statusVars;
|
||||
in
|
||||
''
|
||||
# Calculate overall status
|
||||
OVERALL_STATUS=0
|
||||
${lib.concatMapStringsSep "\n" (var: "if [ \$${var} -ne 0 ]; then OVERALL_STATUS=1; fi") statusVars}
|
||||
|
||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
HOSTNAME=$(hostname)
|
||||
|
||||
SUMMARY=$(cat << EOF
|
||||
# Backup Chain Complete
|
||||
|
||||
**Host:** $HOSTNAME
|
||||
**Timestamp:** $TIMESTAMP
|
||||
**Overall Status:** $([ $OVERALL_STATUS -eq 0 ] && echo '✅ Success' || echo '⚠️ Failure')
|
||||
|
||||
## Service Status:
|
||||
${statusSummaryLines}
|
||||
|
||||
**Log Path:** $LOG_FILE
|
||||
EOF)
|
||||
|
||||
if [ $OVERALL_STATUS -eq 0 ]; then
|
||||
${notify "✅ Backup Success" "$SUMMARY" "$LOG_FILE"}
|
||||
else
|
||||
${notify "⚠️ Backup Issues" "$SUMMARY" "$LOG_FILE"}
|
||||
fi
|
||||
|
||||
exit $OVERALL_STATUS
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
imports = lib.custom.scanPaths ./.;
|
||||
|
||||
systemd.services.backup-chain = {
|
||||
description = "Orchestrated Backup Chain";
|
||||
path = with pkgs; [
|
||||
apprise
|
||||
coreutils
|
||||
findutils
|
||||
gawk
|
||||
gnugrep
|
||||
hostname
|
||||
systemd
|
||||
util-linux
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
Nice = 19;
|
||||
IOSchedulingClass = "idle";
|
||||
CPUSchedulingPolicy = "idle";
|
||||
};
|
||||
|
||||
script = ''
|
||||
${scriptPrelude}
|
||||
log "Initializing backup chain on $(hostname)"
|
||||
|
||||
${serviceExecution}
|
||||
|
||||
log "Finalizing backup chain"
|
||||
${notificationLogic}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.backup-chain = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 03:00:00";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "5min";
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.apprise ];
|
||||
systemd.tmpfiles.rules = [ "d ${logDir} 0755 root root -" ];
|
||||
}
|
|
@ -34,4 +34,18 @@
|
|||
to = if builtins.isAttrs smtp then recipient else smtp.user;
|
||||
in
|
||||
"mailtos://_?user=${smtpUser}&pass=${smtpPass}&smtp=${smtpHost}&from=${smtpFrom}&to=${to}";
|
||||
|
||||
# Get the primary monitor from a list of monitors
|
||||
# Falls back to first monitor if no primary is set
|
||||
getPrimaryMonitor =
|
||||
monitors:
|
||||
let
|
||||
primaryMonitors = builtins.filter (m: m.primary or false) monitors;
|
||||
in
|
||||
if builtins.length primaryMonitors > 0 then
|
||||
builtins.head primaryMonitors
|
||||
else if builtins.length monitors > 0 then
|
||||
builtins.head monitors
|
||||
else
|
||||
null;
|
||||
}
|
||||
|
|
|
@ -13,10 +13,6 @@
|
|||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
noBar = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
width = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
example = 1920;
|
||||
|
@ -44,22 +40,21 @@
|
|||
transform = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 0;
|
||||
description = "Screen orientation: 0 = landscape, 1 = portrait left, 2 = portrait right, 3 = landscape flipped";
|
||||
};
|
||||
enabled = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
workspace = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = "Defines a workspace that should persist on this monitor.";
|
||||
default = null;
|
||||
hdr = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
vrr = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "Variable Refresh Rate aka Adaptive Sync aka AMD FreeSync.\nValues are oriented towards hyprland's vrr values which are:\n0 = off, 1 = on, 2 = fullscreen only\nhttps://wiki.hyprland.org/Configuring/Variables/#misc";
|
||||
default = 0;
|
||||
type = lib.types.bool;
|
||||
description = "Variable Refresh Rate aka Adaptive Sync aka AMD FreeSync.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
404
modules/nixos/backups.nix
Normal file
404
modules/nixos/backups.nix
Normal file
|
@ -0,0 +1,404 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.backup;
|
||||
|
||||
# Helper functions
|
||||
safeName = name: replaceStrings [ "-" ] [ "_" ] name;
|
||||
statusVar = name: "STATUS_${safeName name}";
|
||||
findLatestLog = pattern: path: ''
|
||||
find "${path}" -name "${pattern}" -type f -printf "%T@ %p\\n" 2>/dev/null \
|
||||
| sort -nr | head -1 | cut -d' ' -f2
|
||||
'';
|
||||
|
||||
# Borg service generator
|
||||
mkBorgService =
|
||||
job:
|
||||
let
|
||||
borgCommon = ''
|
||||
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
||||
export BORG_NON_INTERACTIVE=yes
|
||||
'';
|
||||
|
||||
initRepo = ''
|
||||
if [ ! -d "${job.repo}" ]; then
|
||||
mkdir -p "${job.repo}"
|
||||
${pkgs.borgbackup}/bin/borg init --encryption=none "${job.repo}"
|
||||
fi
|
||||
'';
|
||||
|
||||
extractStats = ''
|
||||
{
|
||||
echo -e "\n==== BACKUP SUMMARY ====\n"
|
||||
grep -A10 "Archive name:" $LOG_FILE || echo "No archive stats found"
|
||||
echo -e "\n=== Compression ===\n"
|
||||
grep "Compressed size:" $LOG_FILE || echo "No compression stats found"
|
||||
echo -e "\n=== Duration ===\n"
|
||||
grep "Duration:" $LOG_FILE || echo "No duration stats found"
|
||||
grep "Throughput:" $LOG_FILE || echo "No throughput stats found"
|
||||
echo -e "\n=== Repository ===\n"
|
||||
${pkgs.borgbackup}/bin/borg info ${job.repo} --last 1 2>/dev/null || echo "Could not get repository info"
|
||||
echo -e "\n=== Storage Space ===\n"
|
||||
df -h ${job.repo} | grep -v "Filesystem" || echo "Could not get storage info"
|
||||
} > $LOG_FILE.stats
|
||||
STATS=$(cat $LOG_FILE.stats || echo "No stats available")
|
||||
'';
|
||||
|
||||
excludeArgs = concatMapStringsSep " " (pattern: "--exclude '${pattern}'") job.excludePatterns;
|
||||
in
|
||||
{
|
||||
services."backup-${job.name}" = {
|
||||
description = "Backup ${job.title} with Borg";
|
||||
path = with pkgs; [
|
||||
borgbackup
|
||||
coreutils
|
||||
gnugrep
|
||||
hostname
|
||||
util-linux
|
||||
gawk
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
IOSchedulingClass = "idle";
|
||||
CPUSchedulingPolicy = "idle";
|
||||
Nice = 19;
|
||||
};
|
||||
|
||||
script = ''
|
||||
${borgCommon}
|
||||
${optionalString job.verbose "set -x"}
|
||||
|
||||
LOG_FILE="/tmp/borg-${job.name}-backup-$(date +%Y%m%d-%H%M%S).log"
|
||||
${initRepo}
|
||||
|
||||
echo "Starting ${job.title} backup at $(date)" > $LOG_FILE
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
${pkgs.borgbackup}/bin/borg create \
|
||||
--stats \
|
||||
--compression ${job.compression} \
|
||||
${excludeArgs} \
|
||||
${job.repo}::${job.name}-$(date +%Y-%m-%d_%H%M%S) \
|
||||
${job.sourcePath} >> $LOG_FILE 2>&1
|
||||
|
||||
BACKUP_STATUS=$?
|
||||
echo "Total time: $(($(date +%s) - START_TIME)) seconds" >> $LOG_FILE
|
||||
${extractStats}
|
||||
|
||||
echo -e "\nPruning old backups..." >> $LOG_FILE
|
||||
${pkgs.borgbackup}/bin/borg prune \
|
||||
--keep-daily ${toString job.keepDaily} \
|
||||
--keep-weekly ${toString job.keepWeekly} \
|
||||
--keep-monthly ${toString job.keepMonthly} \
|
||||
${job.repo} >> $LOG_FILE 2>&1
|
||||
|
||||
PRUNE_STATUS=$?
|
||||
|
||||
rm -f $LOG_FILE.stats
|
||||
exit $BACKUP_STATUS
|
||||
'';
|
||||
};
|
||||
}
|
||||
// optionalAttrs (job.schedule != null) {
|
||||
timers."backup-${job.name}" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = job.schedule;
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "5min";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Orchestration service
|
||||
mkChainService =
|
||||
let
|
||||
allJobs = cfg.jobs ++ cfg.maintenanceJobs;
|
||||
initVars = concatMapStringsSep "\n" (j: "${statusVar j.name}=1") allJobs;
|
||||
|
||||
runJob = job: ''
|
||||
# Determine log pattern
|
||||
LOG_PATTERN="${if job.logPattern != "" then job.logPattern else "borg-${job.name}-backup-*.log"}"
|
||||
|
||||
log "Starting ${job.title}..."
|
||||
|
||||
systemctl start ${if job ? service then job.service else "backup-${job.name}"} || true
|
||||
|
||||
# Wait for service to complete and get its exit status
|
||||
while systemctl is-active ${
|
||||
if job ? service then job.service else "backup-${job.name}"
|
||||
} >/dev/null 2>&1; do
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Get the actual service exit status
|
||||
${statusVar job.name}=$(systemctl show ${
|
||||
if job ? service then job.service else "backup-${job.name}"
|
||||
} --property=ExecMainStatus --value)
|
||||
log "${job.title} completed with status $${statusVar job.name}"
|
||||
|
||||
# Give logs time to be written
|
||||
sleep 2
|
||||
|
||||
SERVICE_LOG=$(${findLatestLog "$LOG_PATTERN" "${job.logPath}"})
|
||||
if [ -n "$SERVICE_LOG" ] && [ -r "$SERVICE_LOG" ]; then
|
||||
log "Appending ${job.title} log: $SERVICE_LOG"
|
||||
echo -e "\n\n===== ${job.title} LOG =====\n" >> "$LOG_FILE"
|
||||
cat "$SERVICE_LOG" >> "$LOG_FILE" 2>/dev/null || echo "Could not read log file" >> "$LOG_FILE"
|
||||
else
|
||||
log "No readable log found for ${job.title} (pattern: $LOG_PATTERN in ${job.logPath})"
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.backup-chain = {
|
||||
description = "Backup Orchestration Chain";
|
||||
path = with pkgs; [
|
||||
apprise
|
||||
coreutils
|
||||
findutils
|
||||
gnugrep
|
||||
hostname
|
||||
systemd
|
||||
util-linux
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
Nice = 19;
|
||||
IOSchedulingClass = "idle";
|
||||
CPUSchedulingPolicy = "idle";
|
||||
};
|
||||
|
||||
script = ''
|
||||
set -uo pipefail
|
||||
LOG_FILE="${cfg.logDir}/backup-chain-$(date +%Y%m%d-%H%M%S).log"
|
||||
mkdir -p "${cfg.logDir}"
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
|
||||
|
||||
# Initialize status tracking
|
||||
declare -A JOB_STATUS
|
||||
${concatMapStrings (j: "JOB_STATUS['${j.name}']=1\n") allJobs}
|
||||
|
||||
log "Starting backup chain on $(hostname)"
|
||||
|
||||
${concatMapStrings (job: ''
|
||||
log "Starting ${job.title}..."
|
||||
systemctl start ${if job ? service then job.service else "backup-${job.name}"} || true
|
||||
|
||||
# Wait for completion
|
||||
while systemctl is-active ${
|
||||
if job ? service then job.service else "backup-${job.name}"
|
||||
} >/dev/null 2>&1; do
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Capture exit status
|
||||
JOB_STATUS['${job.name}']=$(systemctl show ${
|
||||
if job ? service then job.service else "backup-${job.name}"
|
||||
} --property=ExecMainStatus --value)
|
||||
log "${job.title} completed with status ''${JOB_STATUS['${job.name}']}"
|
||||
|
||||
# Give logs time to be written
|
||||
sleep 2
|
||||
|
||||
# Find and append logs
|
||||
LOG_PATTERN="${if job.logPattern != "" then job.logPattern else "borg-${job.name}-backup-*.log"}"
|
||||
SERVICE_LOG=$(${findLatestLog "$LOG_PATTERN" "${job.logPath}"})
|
||||
if [ -n "$SERVICE_LOG" ] && [ -r "$SERVICE_LOG" ]; then
|
||||
log "Appending ${job.title} log: $SERVICE_LOG"
|
||||
echo -e "\n\n===== ${job.title} LOG =====\n" >> "$LOG_FILE"
|
||||
cat "$SERVICE_LOG" >> "$LOG_FILE" 2>/dev/null || echo "Could not read log file" >> "$LOG_FILE"
|
||||
else
|
||||
log "No readable log found for ${job.title} (pattern: $LOG_PATTERN in ${job.logPath})"
|
||||
fi
|
||||
'') allJobs}
|
||||
|
||||
# Calculate overall status
|
||||
OVERALL_STATUS=0
|
||||
for status in "''${JOB_STATUS[@]}"; do
|
||||
if [ "$status" -ne 0 ]; then
|
||||
OVERALL_STATUS=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Build summary
|
||||
HOSTNAME=$(hostname)
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Build job status lines
|
||||
JOB_LINES=""
|
||||
${concatMapStrings (job: ''
|
||||
if [ ''${JOB_STATUS['${job.name}']} -eq 0 ]; then
|
||||
STATUS_ICON="✅"
|
||||
else
|
||||
STATUS_ICON="❌"
|
||||
fi
|
||||
JOB_LINES="$JOB_LINES- **${job.title}:** $STATUS_ICON (Exit: ''${JOB_STATUS['${job.name}']})
|
||||
"
|
||||
'') allJobs}
|
||||
|
||||
# Create the final summary
|
||||
SUMMARY="# Backup Chain Complete
|
||||
|
||||
**Host:** $HOSTNAME
|
||||
**Timestamp:** $TIMESTAMP
|
||||
**Overall Status:** $([ $OVERALL_STATUS -eq 0 ] && echo '✅ Success' || echo '⚠️ Failure')
|
||||
|
||||
## Job Status:
|
||||
$JOB_LINES
|
||||
**Log Path:** $LOG_FILE"
|
||||
|
||||
# Send notification
|
||||
${pkgs.apprise}/bin/apprise -vv -i "markdown" \
|
||||
-t "Backup $([ $OVERALL_STATUS -eq 0 ] && echo '✅' || echo '⚠️')" \
|
||||
-b "$SUMMARY" \
|
||||
--attach "file://$LOG_FILE" \
|
||||
"${cfg.notificationUrl}" || true
|
||||
|
||||
exit $OVERALL_STATUS
|
||||
'';
|
||||
};
|
||||
|
||||
timers.backup-chain = mkIf cfg.enableChainTimer {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = cfg.chainSchedule;
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "5min";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Job types
|
||||
borgJobType = types.submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
description = "Unique service name";
|
||||
};
|
||||
title = mkOption {
|
||||
type = types.str;
|
||||
description = "Human-readable title";
|
||||
};
|
||||
repo = mkOption {
|
||||
type = types.str;
|
||||
description = "Borg repository path";
|
||||
};
|
||||
sourcePath = mkOption {
|
||||
type = types.str;
|
||||
description = "Path to back up";
|
||||
};
|
||||
schedule = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Timer schedule for standalone execution";
|
||||
};
|
||||
keepDaily = mkOption {
|
||||
type = types.int;
|
||||
default = 7;
|
||||
};
|
||||
keepWeekly = mkOption {
|
||||
type = types.int;
|
||||
default = 4;
|
||||
};
|
||||
keepMonthly = mkOption {
|
||||
type = types.int;
|
||||
default = 3;
|
||||
};
|
||||
verbose = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
compression = mkOption {
|
||||
type = types.str;
|
||||
default = "zstd,15";
|
||||
};
|
||||
excludePatterns = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"*.tmp"
|
||||
"*/tmp/*"
|
||||
];
|
||||
};
|
||||
logPattern = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Log pattern for chain to find";
|
||||
};
|
||||
logPath = mkOption {
|
||||
type = types.str;
|
||||
default = "/tmp";
|
||||
description = "Path to search for logs";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
maintenanceJobType = types.submodule {
|
||||
options = {
|
||||
name = mkOption { type = types.str; };
|
||||
title = mkOption { type = types.str; };
|
||||
service = mkOption {
|
||||
type = types.str;
|
||||
description = "Systemd service to run";
|
||||
};
|
||||
logPattern = mkOption { type = types.str; };
|
||||
logPath = mkOption {
|
||||
type = types.str;
|
||||
default = "/tmp";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
options.services.backup = {
|
||||
enable = mkEnableOption "backup system";
|
||||
notificationUrl = mkOption { type = types.str; };
|
||||
logDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/log/backups";
|
||||
};
|
||||
jobs = mkOption {
|
||||
type = types.listOf borgJobType;
|
||||
default = [ ];
|
||||
};
|
||||
maintenanceJobs = mkOption {
|
||||
type = types.listOf maintenanceJobType;
|
||||
default = [ ];
|
||||
};
|
||||
enableChainTimer = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
chainSchedule = mkOption {
|
||||
type = types.str;
|
||||
default = "*-*-* 03:00:00";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs; [
|
||||
borgbackup
|
||||
apprise
|
||||
];
|
||||
|
||||
systemd = mkMerge [
|
||||
# Create log directory
|
||||
{ tmpfiles.rules = [ "d ${cfg.logDir} 0755 root root -" ]; }
|
||||
|
||||
# Individual backup services
|
||||
(mkMerge (map mkBorgService cfg.jobs))
|
||||
|
||||
# Backup chain orchestration
|
||||
(mkIf (cfg.jobs != [ ] || cfg.maintenanceJobs != [ ]) mkChainService)
|
||||
];
|
||||
};
|
||||
}
|
|
@ -90,7 +90,7 @@ Each system in `hosts/nixos/<hostname>/` contains:
|
|||
| ---------- | ------------- | ---------------------- | --------------------------- | -------------------------------- |
|
||||
| **rune** | Desktop | My workstation | Ryzen 9 7900X3D, RX 9070 XT | Gaming, Development, VMs |
|
||||
| **gojo** | Desktop | Giovanni's workstation | Ryzen 7 7800X3D, RX 7900 XT | Gaming, Development |
|
||||
| **haze** | Desktop | Cesar's workstation | Ryzen 7, RX 6950 XT | Gaming, Development |
|
||||
| **haze** | Desktop | Cesar's workstation | Ryzen 5 7600x, RX 7600 | Gaming, Development |
|
||||
| **caenus** | Server | Oracle VPS | ARM 4vCPU, 24GB RAM, 200GB | FRP, Public IP |
|
||||
| **sock** | Server | Backup & Storage | Intel N150 | Komodo (Docker), Backups, Newt |
|
||||
| **cloud** | LXC Container | Storage & NFS | 4C/4GB | File storage, NFS, Newt |
|
||||
|
|
Loading…
Add table
Reference in a new issue